2024-05-21 23:20:34 +02:00
import ' package:flutter/material.dart ' ;
2024-06-08 20:01:23 +02:00
import ' package:flutter/services.dart ' ;
2024-06-07 23:44:32 +02:00
import ' package:font_awesome_flutter/font_awesome_flutter.dart ' ;
import ' package:intl/intl.dart ' ;
2024-05-21 23:20:34 +02:00
import ' package:provider/provider.dart ' ;
2024-06-08 20:01:23 +02:00
import ' package:share_plus/share_plus.dart ' ;
2024-05-21 23:20:34 +02:00
import ' package:simplecloudnotifier/api/api_client.dart ' ;
import ' package:simplecloudnotifier/components/layout/scaffold.dart ' ;
2024-06-07 23:44:32 +02:00
import ' package:simplecloudnotifier/models/channel.dart ' ;
import ' package:simplecloudnotifier/models/keytoken.dart ' ;
2024-06-15 21:29:51 +02:00
import ' package:simplecloudnotifier/models/scn_message.dart ' ;
2024-06-12 01:17:00 +02:00
import ' package:simplecloudnotifier/models/user.dart ' ;
2024-06-02 17:09:57 +02:00
import ' package:simplecloudnotifier/state/app_auth.dart ' ;
2024-06-13 17:00:08 +02:00
import ' package:simplecloudnotifier/state/app_bar_state.dart ' ;
2024-06-08 20:01:23 +02:00
import ' package:simplecloudnotifier/utils/toaster.dart ' ;
2024-06-08 12:55:58 +02:00
import ' package:simplecloudnotifier/utils/ui.dart ' ;
2024-05-21 23:20:34 +02:00
class MessageViewPage extends StatefulWidget {
2024-05-23 17:41:51 +02:00
const MessageViewPage ( { super . key , required this . message } ) ;
2024-05-21 23:20:34 +02:00
2024-06-15 21:29:51 +02:00
final SCNMessage message ; // Potentially trimmed
2024-05-21 23:20:34 +02:00
@ override
State < MessageViewPage > createState ( ) = > _MessageViewPageState ( ) ;
}
class _MessageViewPageState extends State < MessageViewPage > {
2024-06-15 21:29:51 +02:00
late Future < ( SCNMessage , ChannelPreview , KeyTokenPreview , UserPreview ) > ? mainFuture ;
( SCNMessage , ChannelPreview , KeyTokenPreview , UserPreview ) ? mainFutureSnapshot = null ;
2024-06-07 23:44:32 +02:00
static final _dateFormat = DateFormat ( ' yyyy-MM-dd kk:mm ' ) ;
2024-05-21 23:20:34 +02:00
2024-06-08 20:01:23 +02:00
bool _monospaceMode = false ;
2024-05-21 23:20:34 +02:00
@ override
void initState ( ) {
2024-06-07 23:44:32 +02:00
mainFuture = fetchData ( ) ;
2024-06-13 17:00:08 +02:00
super . initState ( ) ;
2024-05-21 23:20:34 +02:00
}
2024-06-15 21:29:51 +02:00
Future < ( SCNMessage , ChannelPreview , KeyTokenPreview , UserPreview ) > fetchData ( ) async {
2024-06-13 17:00:08 +02:00
try {
await Future . delayed ( const Duration ( seconds: 0 ) , ( ) { } ) ; // this is annoyingly important - otherwise we call setLoadingIndeterminate directly in initStat() and get an exception....
2024-05-21 23:20:34 +02:00
2024-06-13 17:00:08 +02:00
AppBarState ( ) . setLoadingIndeterminate ( true ) ;
2024-06-07 23:44:32 +02:00
2024-06-13 17:00:08 +02:00
final acc = Provider . of < AppAuth > ( context , listen: false ) ;
2024-06-07 23:44:32 +02:00
2024-06-13 17:00:08 +02:00
final msg = await APIClient . getMessage ( acc , widget . message . messageID ) ;
2024-06-07 23:44:32 +02:00
2024-06-13 17:00:08 +02:00
final fut_chn = APIClient . getChannelPreview ( acc , msg . channelID ) ;
final fut_key = APIClient . getKeyTokenPreview ( acc , msg . usedKeyID ) ;
final fut_usr = APIClient . getUserPreview ( acc , msg . senderUserID ) ;
2024-06-08 20:01:23 +02:00
2024-06-13 17:00:08 +02:00
final chn = await fut_chn ;
final key = await fut_key ;
final usr = await fut_usr ;
2024-06-08 20:01:23 +02:00
2024-06-13 17:00:08 +02:00
//await Future.delayed(const Duration(seconds: 10), () {});
2024-06-08 20:01:23 +02:00
2024-06-13 17:00:08 +02:00
final r = ( msg , chn , key , usr ) ;
2024-06-08 20:01:23 +02:00
2024-06-13 17:00:08 +02:00
mainFutureSnapshot = r ;
return r ;
} finally {
AppBarState ( ) . setLoadingIndeterminate ( false ) ;
}
2024-05-21 23:20:34 +02:00
}
@ override
void dispose ( ) {
super . dispose ( ) ;
}
@ override
Widget build ( BuildContext context ) {
return SCNScaffold (
title: ' Message ' ,
2024-05-23 17:41:51 +02:00
showSearch: false ,
2024-06-08 20:01:23 +02:00
showShare: true ,
onShare: _share ,
2024-06-15 21:29:51 +02:00
child: FutureBuilder < ( SCNMessage , ChannelPreview , KeyTokenPreview , UserPreview ) > (
2024-06-07 23:44:32 +02:00
future: mainFuture ,
2024-05-21 23:20:34 +02:00
builder: ( context , snapshot ) {
if ( snapshot . hasData ) {
2024-06-12 01:17:00 +02:00
final ( msg , chn , tok , usr ) = snapshot . data ! ;
2024-06-13 17:00:08 +02:00
return _buildMessageView ( context , msg , chn , tok , usr ) ;
2024-05-21 23:20:34 +02:00
} else if ( snapshot . hasError ) {
return Center ( child: Text ( ' ${ snapshot . error } ' ) ) ; //TODO nice error page
2024-05-23 17:41:51 +02:00
} else if ( ! widget . message . trimmed ) {
2024-06-13 17:00:08 +02:00
return _buildMessageView ( context , widget . message , null , null , null ) ;
2024-05-23 17:41:51 +02:00
} else {
return const Center ( child: CircularProgressIndicator ( ) ) ;
2024-05-21 23:20:34 +02:00
}
} ,
) ,
) ;
}
2024-05-23 17:41:51 +02:00
2024-06-08 20:01:23 +02:00
void _share ( ) async {
var msg = widget . message ;
if ( mainFutureSnapshot ! = null ) {
2024-06-12 01:17:00 +02:00
( msg , _ , _ , _ ) = mainFutureSnapshot ! ;
2024-06-08 20:01:23 +02:00
}
if ( msg . content ! = null ) {
final result = await Share . share ( msg . content ! , subject: msg . title ) ;
if ( result . status = = ShareResultStatus . unavailable ) {
Toaster . error ( ' Error ' , " Failed to open share dialog " ) ;
}
} else {
final result = await Share . share ( msg . title ) ;
if ( result . status = = ShareResultStatus . unavailable ) {
Toaster . error ( ' Error ' , " Failed to open share dialog " ) ;
}
}
}
2024-06-15 21:29:51 +02:00
Widget _buildMessageView ( BuildContext context , SCNMessage message , ChannelPreview ? channel , KeyTokenPreview ? token , UserPreview ? user ) {
2024-06-08 20:01:23 +02:00
final userAccUserID = context . select < AppAuth , String ? > ( ( v ) = > v . userID ) ;
2024-06-07 23:44:32 +02:00
return SingleChildScrollView (
child: Padding (
padding: const EdgeInsets . fromLTRB ( 24 , 16 , 24 , 16 ) ,
child: Column (
crossAxisAlignment: CrossAxisAlignment . stretch ,
children: [
2024-06-13 17:00:08 +02:00
. . . _buildMessageHeader ( context , message , channel ) ,
2024-06-07 23:44:32 +02:00
SizedBox ( height: 8 ) ,
2024-06-12 01:17:00 +02:00
if ( message . content ! = null ) . . . _buildMessageContent ( context , message ) ,
2024-06-07 23:44:32 +02:00
SizedBox ( height: 8 ) ,
if ( message . senderName ! = null ) _buildMetaCard ( context , FontAwesomeIcons . solidSignature , ' Sender ' , [ message . senderName ! ] , ( ) = > { /*TODO*/ } ) ,
2024-06-23 13:31:10 +02:00
_buildMetaCard (
context ,
FontAwesomeIcons . solidGearCode ,
' KeyToken ' ,
[
message . usedKeyID ,
token ? . name ? ? ' ... ' ,
] ,
( ) = > { /*TODO*/ } ) ,
_buildMetaCard (
context ,
FontAwesomeIcons . solidIdCardClip ,
' MessageID ' ,
[
message . messageID ,
message . userMessageID ? ? ' ' ,
] ,
null ) ,
_buildMetaCard (
context ,
FontAwesomeIcons . solidSnake ,
' Channel ' ,
[
message . channelID ,
channel ? . displayName ? ? message . channelInternalName ,
] ,
( ) = > { /*TODO*/ } ) ,
_buildMetaCard (
context ,
FontAwesomeIcons . solidTimer ,
' Timestamp ' ,
[
message . timestamp ,
] ,
null ) ,
_buildMetaCard (
context ,
FontAwesomeIcons . solidUser ,
' User ' ,
[
user ? . userID ? ? ' ... ' ,
user ? . username ? ? ' ' ,
] ,
( ) = > { /*TODO*/ } ) , //TODO
_buildMetaCard (
context ,
FontAwesomeIcons . solidBolt ,
' Priority ' ,
[
_prettyPrintPriority ( message . priority ) ,
] ,
( ) = > { /*TODO*/ } ) , //TODO
2024-06-08 20:01:23 +02:00
if ( message . senderUserID = = userAccUserID ) UI . button ( text: " Delete Message " , onPressed: ( ) { /*TODO*/ } , color: Colors . red [ 900 ] ) ,
2024-06-07 23:44:32 +02:00
] ,
) ,
) ,
) ;
}
2024-06-15 21:29:51 +02:00
String _resolveChannelName ( ChannelPreview ? channel , SCNMessage message ) {
2024-06-12 01:17:00 +02:00
return channel ? . displayName ? ? message . channelInternalName ;
2024-06-07 23:44:32 +02:00
}
2024-06-15 21:29:51 +02:00
List < Widget > _buildMessageHeader ( BuildContext context , SCNMessage message , ChannelPreview ? channel ) {
2024-06-07 23:44:32 +02:00
return [
Row (
children: [
2024-06-08 12:55:58 +02:00
UI . channelChip (
context: context ,
text: _resolveChannelName ( channel , message ) ,
2024-06-07 23:44:32 +02:00
margin: const EdgeInsets . fromLTRB ( 0 , 0 , 4 , 0 ) ,
2024-06-08 12:55:58 +02:00
fontSize: 16 ,
2024-06-07 23:44:32 +02:00
) ,
Expanded ( child: SizedBox ( ) ) ,
Text ( _dateFormat . format ( DateTime . parse ( message . timestamp ) ) , style: const TextStyle ( fontSize: 14 ) ) ,
] ,
) ,
SizedBox ( height: 8 ) ,
2024-06-15 12:37:38 +02:00
Text ( _preformatTitle ( message ) , style: const TextStyle ( fontSize: 18 , fontWeight: FontWeight . bold ) ) ,
2024-06-07 23:44:32 +02:00
] ;
}
2024-06-15 21:29:51 +02:00
List < Widget > _buildMessageContent ( BuildContext context , SCNMessage message ) {
2024-06-07 23:44:32 +02:00
return [
Row (
children: [
2024-06-08 20:01:23 +02:00
if ( message . priority = = 2 ) FaIcon ( FontAwesomeIcons . solidTriangleExclamation , size: 16 , color: Colors . red [ 900 ] ) ,
if ( message . priority = = 0 ) FaIcon ( FontAwesomeIcons . solidDown , size: 16 , color: Colors . lightBlue [ 900 ] ) ,
2024-06-07 23:44:32 +02:00
Expanded ( child: SizedBox ( ) ) ,
2024-06-08 12:55:58 +02:00
UI . buttonIconOnly (
2024-06-08 20:01:23 +02:00
onPressed: ( ) {
Clipboard . setData ( new ClipboardData ( text: message . content ? ? ' ' ) ) ;
Toaster . info ( " Clipboard " , ' Copied text to Clipboard ' ) ;
} ,
2024-06-08 12:55:58 +02:00
icon: FontAwesomeIcons . copy ,
2024-06-07 23:44:32 +02:00
) ,
2024-06-08 12:55:58 +02:00
UI . buttonIconOnly (
2024-06-08 20:01:23 +02:00
icon: _monospaceMode ? FontAwesomeIcons . lineColumns : FontAwesomeIcons . alignLeft ,
onPressed: ( ) {
setState ( ( ) {
_monospaceMode = ! _monospaceMode ;
} ) ;
} ,
2024-06-07 23:44:32 +02:00
) ,
] ,
) ,
2024-06-08 20:01:23 +02:00
_monospaceMode
? UI . box (
context: context ,
padding: const EdgeInsets . all ( 4 ) ,
child: SingleChildScrollView (
scrollDirection: Axis . horizontal ,
child: Text (
message . content ? ? ' ' ,
style: TextStyle ( fontFamily: " monospace " , fontFamilyFallback: < String > [ " Courier " ] ) ,
) ,
) ,
borderColor: ( message . priority = = 2 ) ? Colors . red [ 900 ] : null ,
)
: UI . box (
context: context ,
padding: const EdgeInsets . all ( 4 ) ,
child: Text ( message . content ? ? ' ' ) ,
borderColor: ( message . priority = = 2 ) ? Colors . red [ 900 ] : null ,
)
2024-06-07 23:44:32 +02:00
] ;
}
Widget _buildMetaCard ( BuildContext context , IconData icn , String title , List < String > values , void Function ( ) ? action ) {
2024-06-08 12:55:58 +02:00
final container = UI . box (
context: context ,
2024-06-07 23:44:32 +02:00
padding: EdgeInsets . fromLTRB ( 16 , 2 , 4 , 2 ) ,
child: Row (
2024-05-23 17:41:51 +02:00
children: [
2024-06-07 23:44:32 +02:00
FaIcon ( icn , size: 18 ) ,
SizedBox ( width: 16 ) ,
Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Text ( title , style: const TextStyle ( fontSize: 16 , fontWeight: FontWeight . bold ) ) ,
for ( final val in values ) Text ( val , style: const TextStyle ( fontSize: 14 ) ) ,
] ,
) ,
2024-05-23 17:41:51 +02:00
] ,
) ,
) ;
2024-06-07 23:44:32 +02:00
if ( action = = null ) {
return Padding (
padding: EdgeInsets . symmetric ( vertical: 4 , horizontal: 0 ) ,
child: container ,
) ;
} else {
return Padding (
padding: EdgeInsets . symmetric ( vertical: 4 , horizontal: 0 ) ,
child: InkWell (
splashColor: Theme . of ( context ) . splashColor ,
onTap: action ,
child: container ,
) ,
) ;
}
2024-05-23 17:41:51 +02:00
}
2024-06-15 12:37:38 +02:00
2024-06-15 21:29:51 +02:00
String _preformatTitle ( SCNMessage message ) {
2024-06-15 12:37:38 +02:00
return message . title . replaceAll ( ' \n ' , ' ' ) . replaceAll ( ' \r ' , ' ' ) . replaceAll ( ' \t ' , ' ' ) ;
}
2024-06-16 00:46:46 +02:00
String _prettyPrintPriority ( int priority ) {
switch ( priority ) {
case 0 :
return ' Low (0) ' ;
case 1 :
return ' Normal (1) ' ;
case 2 :
return ' High (2) ' ;
default :
return ' Unknown ( $ priority ) ' ;
}
}
2024-05-21 23:20:34 +02:00
}