import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:provider/provider.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:simplecloudnotifier/api/api_client.dart'; import 'package:simplecloudnotifier/state/application_log.dart'; import 'package:simplecloudnotifier/state/globals.dart'; import 'package:simplecloudnotifier/utils/toaster.dart'; import 'package:simplecloudnotifier/utils/ui.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:simplecloudnotifier/state/app_auth.dart'; class SendRootPage extends StatefulWidget { const SendRootPage({super.key, required this.isVisiblePage}); final bool isVisiblePage; @override State createState() => _SendRootPageState(); } class _SendRootPageState extends State { late TextEditingController _msgTitle; late TextEditingController _msgContent; late TextEditingController _channelName; late TextEditingController _senderName; int _priority = 0; bool _expanded = false; @override void initState() { super.initState(); _msgTitle = TextEditingController(); _msgContent = TextEditingController(); _channelName = TextEditingController(); _senderName = TextEditingController(); } @override void dispose() { _msgTitle.dispose(); _msgContent.dispose(); _channelName.dispose(); _senderName.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Consumer( builder: (context, acc, child) { return SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(16.0), child: _expanded ? _buildExpanded(context, acc) : _buildSimple(context, acc), ), ); }, ); } Widget _buildSimple(BuildContext context, AppAuth acc) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ _buildQRCode(context, acc), const SizedBox(height: 16), FractionallySizedBox( widthFactor: 1.0, child: TextField( controller: _msgTitle, decoration: const InputDecoration( border: OutlineInputBorder(), labelText: 'Title', ), ), ), const SizedBox(height: 16), FractionallySizedBox( widthFactor: 1.0, child: TextField( controller: _msgContent, decoration: const InputDecoration( border: OutlineInputBorder(), labelText: 'Text', ), minLines: 2, maxLines: null, keyboardType: TextInputType.multiline, ), ), const SizedBox(height: 16), Row( children: [ Expanded( child: UI.button( text: 'Send', onPressed: () { _sendSimple(acc); }, color: Theme.of(context).colorScheme.primary, textColor: Theme.of(context).colorScheme.onPrimary, ), ), const SizedBox(width: 8), UI.buttonIconOnly( icon: FontAwesomeIcons.layerPlus, onPressed: _openExpanded, square: true, color: Theme.of(context).colorScheme.secondary, iconColor: Theme.of(context).colorScheme.onSecondary, ), ], ), const SizedBox(height: 32), ], ); } Widget _buildExpanded(BuildContext context, AppAuth acc) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ FractionallySizedBox( widthFactor: 1.0, child: TextField( controller: _channelName, decoration: const InputDecoration( border: OutlineInputBorder(), labelText: 'Channel', ), ), ), const SizedBox(height: 16), FractionallySizedBox( widthFactor: 1.0, child: TextField( controller: _msgTitle, decoration: const InputDecoration( border: OutlineInputBorder(), labelText: 'Title', ), ), ), const SizedBox(height: 16), FractionallySizedBox( widthFactor: 1.0, child: TextField( controller: _senderName, decoration: const InputDecoration( border: OutlineInputBorder(), labelText: 'SenderName', ), ), ), const SizedBox(height: 16), SegmentedButton( showSelectedIcon: false, segments: const >[ ButtonSegment(value: 0, label: Text('Low Priority')), ButtonSegment(value: 1, label: Text('Normal')), ButtonSegment(value: 2, label: Text('High Priority')), ], selected: {_priority}, onSelectionChanged: (Set newSelection) { setState(() { _priority = newSelection.isEmpty ? 1 : newSelection.first; }); }, ), const SizedBox(height: 16), FractionallySizedBox( widthFactor: 1.0, child: TextField( controller: _msgContent, decoration: const InputDecoration( border: OutlineInputBorder(), labelText: 'Text', ), minLines: 6, maxLines: null, keyboardType: TextInputType.multiline, ), ), const SizedBox(height: 16), Row( children: [ Expanded( child: UI.button( text: 'Send', onPressed: () { _sendExpanded(acc); }, color: Theme.of(context).colorScheme.primary, textColor: Theme.of(context).colorScheme.onPrimary, ), ), const SizedBox(width: 8), UI.buttonIconOnly( icon: FontAwesomeIcons.squareDashed, onPressed: _closeExpanded, square: true, color: Theme.of(context).colorScheme.secondary, iconColor: Theme.of(context).colorScheme.onSecondary, ), ], ), const SizedBox(height: 32), ], ); } Widget _buildQRCode(BuildContext context, AppAuth acc) { if (!acc.isAuth()) { return const Placeholder(); } return FutureBuilder( future: acc.loadUser(force: false), builder: ((context, snapshot) { if (snapshot.connectionState == ConnectionState.active || snapshot.connectionState == ConnectionState.waiting) { return const SizedBox( width: 300.0, height: 300.0, child: Center(child: CircularProgressIndicator()), ); } if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); //TODO better error display } if (snapshot.connectionState != ConnectionState.done) { return Text('...'); //? } var url = (acc.tokenSend == null) ? 'https://simplecloudnotifier.de?preset_user_id=${acc.userID}' : 'https://simplecloudnotifier.de?preset_user_id=${acc.userID}&preset_user_key=${acc.tokenSend}'; return GestureDetector( onTap: () { _openWeb(url); }, child: QrImageView( data: url, version: QrVersions.auto, size: 300.0, eyeStyle: QrEyeStyle( eyeShape: QrEyeShape.square, color: Theme.of(context).textTheme.bodyLarge?.color, ), dataModuleStyle: QrDataModuleStyle( dataModuleShape: QrDataModuleShape.square, color: Theme.of(context).textTheme.bodyLarge?.color, ), ), ); }), ); } void _sendSimple(AppAuth acc) async { if (!acc.isAuth()) { Toaster.error("Error", 'Must be logged in to send messages'); return; } try { await APIClient.sendMessage(acc.userID!, acc.tokenSend!, _msgContent.text); Toaster.success("Success", 'Message sent'); setState(() { _msgTitle.clear(); _msgContent.clear(); }); } catch (e, stackTrace) { Toaster.error("Error", 'Failed to send message: ${e.toString()}'); ApplicationLog.error('Failed to send message', trace: stackTrace); } } void _sendExpanded(AppAuth acc) async { if (!acc.isAuth()) { Toaster.error("Error", 'Must be logged in to send messages'); return; } try { await APIClient.sendMessage(acc.userID!, acc.tokenSend!, _msgContent.text, channel: _channelName.text, senderName: _senderName.text, priority: _priority); Toaster.success("Success", 'Message sent'); setState(() { _msgTitle.clear(); _msgContent.clear(); }); } catch (e, stackTrace) { Toaster.error("Error", 'Failed to send message: ${e.toString()}'); ApplicationLog.error('Failed to send message', trace: stackTrace); } } void _openWeb(String url) async { try { final Uri uri = Uri.parse(url); ApplicationLog.debug('Opening URL: [ ${uri.toString()} ]'); if (await canLaunchUrl(uri)) { await launchUrl(uri); } else { Toaster.error("Error", 'Cannot open URL on this system'); } } catch (exc, trace) { ApplicationLog.error('Failed to open URL: ' + exc.toString(), additional: 'URL: ${url}', trace: trace); } } void _closeExpanded() { setState(() { _expanded = false; _channelName.clear(); _priority = 1; _senderName.clear(); }); } void _openExpanded() { final userAcc = Provider.of(context, listen: false); setState(() { _expanded = true; _channelName.text = userAcc.getUserOrNull()?.defaultChannel ?? 'main'; _priority = 1; _senderName.text = Globals().deviceName; }); } }