2025-04-13 00:17:06 +02:00
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 ' ;
2025-04-18 14:07:31 +02:00
import ' package:simplecloudnotifier/components/error_display/error_display.dart ' ;
2025-04-13 00:17:06 +02:00
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 < ChannelScannerResultMessageSend > createState ( ) = > _ChannelScannerResultMessageSendState ( ) ;
}
class _ChannelScannerResultMessageSendState extends State < ChannelScannerResultMessageSend > {
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 < AppAuth > ( 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 ) {
2025-04-18 14:07:31 +02:00
return ErrorDisplay ( errorMessage: ' ${ snapshot . error } ' ) ;
2025-04-13 00:17:06 +02:00
}
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 \n Cannot send messages ' ,
textAlign: TextAlign . center ,
style: const TextStyle ( fontStyle: FontStyle . italic ) ,
) ,
if ( widget . value . userKey ! = null ) . . . _buildSend ( context ) ,
] ,
) ;
} ,
) ;
}
List < Widget > _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 ) ,
2025-04-13 01:51:52 +02:00
UI . buttonIconOnly (
icon: FontAwesomeIcons . earthAmericas ,
2025-04-13 00:17:06 +02:00
onPressed: _onOpenWeb ,
2025-04-13 01:51:52 +02:00
square: true ,
2025-04-13 00:17:06 +02:00
color: Theme . of ( context ) . colorScheme . secondary ,
2025-04-13 01:51:52 +02:00
iconColor: Theme . of ( context ) . colorScheme . onSecondary ,
2025-04-13 00:17:06 +02:00
) ,
] ,
) ,
] ;
}
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 ' ) ;
2025-04-13 01:51:52 +02:00
setState ( ( ) {
_ctrlMessage . clear ( ) ;
} ) ;
2025-04-13 00:17:06 +02:00
} 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 < String > 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 " ) ;
}
}