import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:provider/provider.dart'; import 'package:simplecloudnotifier/api/api_client.dart'; import 'package:simplecloudnotifier/models/keytoken.dart'; import 'package:simplecloudnotifier/models/scan_result.dart'; import 'package:simplecloudnotifier/models/user.dart'; import 'package:simplecloudnotifier/state/app_auth.dart'; import 'package:simplecloudnotifier/state/application_log.dart'; import 'package:simplecloudnotifier/utils/toaster.dart'; import 'package:simplecloudnotifier/utils/ui.dart'; import 'package:url_launcher/url_launcher.dart'; class ChannelScannerResultMessageSend extends StatefulWidget { final ScanResultMessageSend value; const ChannelScannerResultMessageSend({required this.value}) : super(); @override State createState() => _ChannelScannerResultMessageSendState(); } class _ChannelScannerResultMessageSendState extends State { Future<(UserPreview, KeyTokenPreview?)?> _fetchDataFuture; _ChannelScannerResultMessageSendState() : _fetchDataFuture = Future.value(null); // Initial dummy future late TextEditingController _ctrlMessage; @override void initState() { super.initState(); _ctrlMessage = TextEditingController(); final auth = Provider.of(context, listen: false); setState(() { _fetchDataFuture = _fetchData(auth); }); } @override void dispose() { _ctrlMessage.dispose(); super.dispose(); } Future<(UserPreview, KeyTokenPreview?)?> _fetchData(AppAuth auth) async { UserPreview? user = null; try { user = await APIClient.getUserPreview(auth, widget.value.userID); } catch (e, stackTrace) { Toaster.error("Error", 'Failed to fetch user preview: ${e.toString()}'); ApplicationLog.error('Failed to fetch user (preview) for ${widget.value.userID}', trace: stackTrace); return null; } KeyTokenPreview? key = null; if (widget.value.userKey != null) { try { key = await APIClient.getKeyTokenPreviewByToken(auth, widget.value.userKey!); } catch (e, stackTrace) { Toaster.error("Error", 'Failed to fetch keytoken preview: ${e.toString()}'); ApplicationLog.error('Failed to fetch keytoken (preview) for ${widget.value.userID}', trace: stackTrace); return null; } } return (user, key); } @override Widget build(BuildContext context) { return FutureBuilder<(UserPreview, KeyTokenPreview?)?>( future: _fetchDataFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); //TODO better error display } if (snapshot.data == null) { return Column( spacing: 32, children: [ Icon(FontAwesomeIcons.solidTriangleExclamation, size: 64, color: Colors.red[900]), Text("Failed to parse QR", textAlign: TextAlign.center), ], ); } final (user, key) = snapshot.data!; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text((widget.value.userKey == null) ? "SCN User" : "SCN User & Key", style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 20), textAlign: TextAlign.center), const SizedBox(height: 16), if (user.username != null) Row( children: [ ConstrainedBox(child: Text("Name: ", style: const TextStyle(fontWeight: FontWeight.bold)), constraints: const BoxConstraints(minWidth: 100)), Expanded(child: SingleChildScrollView(child: Text(user.username!), scrollDirection: Axis.horizontal)), ], ), Row( children: [ ConstrainedBox(child: Text("UserID:", style: const TextStyle(fontWeight: FontWeight.bold)), constraints: const BoxConstraints(minWidth: 100)), Expanded(child: SingleChildScrollView(child: Text(user.userID, style: const TextStyle(fontStyle: FontStyle.italic)), scrollDirection: Axis.horizontal)), ], ), const SizedBox(height: 16), if (key != null) ...[ Row( children: [ ConstrainedBox(child: Text("KeyID:", style: const TextStyle(fontWeight: FontWeight.bold)), constraints: const BoxConstraints(minWidth: 100)), Expanded(child: SingleChildScrollView(child: Text(key.keytokenID, style: const TextStyle(fontStyle: FontStyle.italic)), scrollDirection: Axis.horizontal)), ], ), Row( children: [ ConstrainedBox(child: Text("KeyName:", style: const TextStyle(fontWeight: FontWeight.bold)), constraints: const BoxConstraints(minWidth: 100)), Expanded(child: SingleChildScrollView(child: Text(key.name), scrollDirection: Axis.horizontal)), ], ), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ ConstrainedBox(child: Text("Permissions:", style: const TextStyle(fontWeight: FontWeight.bold)), constraints: const BoxConstraints(minWidth: 100)), Expanded( child: SingleChildScrollView( child: Text(_formatPermissions(key.permissions) + "\n" + (key.allChannels ? "(all channels)" : '(${key.channels.length} channels)')), scrollDirection: Axis.horizontal, ), ), ], ), ], const SizedBox(height: 16), if (widget.value.userKey == null) Text( 'QR Code contains no key\nCannot send messages', textAlign: TextAlign.center, style: const TextStyle(fontStyle: FontStyle.italic), ), if (widget.value.userKey != null) ..._buildSend(context), ], ); }, ); } List _buildSend(BuildContext context) { return [ FractionallySizedBox( widthFactor: 1.0, child: TextField( controller: _ctrlMessage, decoration: const InputDecoration( border: OutlineInputBorder(), labelText: 'Text', ), ), ), const SizedBox(height: 4), Row( children: [ Expanded( child: UI.button( text: 'Send Message', onPressed: _onSend, color: Theme.of(context).colorScheme.primary, textColor: Theme.of(context).colorScheme.onPrimary, ), ), const SizedBox(width: 8), UI.buttonIconOnly( icon: FontAwesomeIcons.earthAmericas, onPressed: _onOpenWeb, square: true, color: Theme.of(context).colorScheme.secondary, iconColor: Theme.of(context).colorScheme.onSecondary, ), ], ), ]; } void _onSend() async { if (_ctrlMessage.text.isEmpty) { Toaster.error("Error", 'Please enter a message'); return; } if (widget.value.userKey == null) return; try { await APIClient.sendMessage(widget.value.userID, widget.value.userKey!, _ctrlMessage.text); Toaster.success("Success", 'Message sent'); setState(() { _ctrlMessage.clear(); }); } catch (e, stackTrace) { Toaster.error("Error", 'Failed to send message: ${e.toString()}'); ApplicationLog.error('Failed to send message', trace: stackTrace); } } void _onOpenWeb() async { try { final Uri uri = Uri.parse(widget.value.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: ${widget.value.url}', trace: trace); } } String _formatPermissions(String v) { var splt = v.split(';'); if (splt.length == 0) return "None"; List result = []; if (splt.contains("A")) result.add(" - Admin"); if (splt.contains("UR")) result.add(" - Read Account"); if (splt.contains("CR")) result.add(" - Read Messages"); if (splt.contains("CS")) result.add(" - Send Messages"); return result.join("\n"); } }