SimpleCloudNotifier/flutter/lib/pages/message_view/message_view.dart

275 lines
9.4 KiB
Dart
Raw Normal View History

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';
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();
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 {
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
AppBarState().setLoadingIndeterminate(true);
2024-06-07 23:44:32 +02:00
final acc = Provider.of<AppAuth>(context, listen: false);
2024-06-07 23:44:32 +02:00
final msg = await APIClient.getMessage(acc, widget.message.messageID);
2024-06-07 23:44:32 +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
final chn = await fut_chn;
final key = await fut_key;
final usr = await fut_usr;
2024-06-08 20:01:23 +02:00
//await Future.delayed(const Duration(seconds: 10), () {});
2024-06-08 20:01:23 +02:00
final r = (msg, chn, key, usr);
2024-06-08 20:01:23 +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!;
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) {
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: [
..._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),
2024-06-25 20:49:40 +02:00
if (message.senderName != null)
UI.metaCard(
context: context,
icon: FontAwesomeIcons.solidSignature,
title: 'Sender',
values: [message.senderName!],
mainAction: () => {/*TODO*/},
),
UI.metaCard(
context: context,
icon: FontAwesomeIcons.solidGearCode,
title: 'KeyToken',
values: [message.usedKeyID, token?.name ?? '...'],
mainAction: () => {/*TODO*/},
),
UI.metaCard(
context: context,
icon: FontAwesomeIcons.solidIdCardClip,
title: 'MessageID',
values: [message.messageID, message.userMessageID ?? ''],
),
UI.metaCard(
context: context,
icon: FontAwesomeIcons.solidSnake,
title: 'Channel',
values: [message.channelID, channel?.displayName ?? message.channelInternalName],
mainAction: () => {/*TODO*/},
),
UI.metaCard(
context: context,
icon: FontAwesomeIcons.solidTimer,
title: 'Timestamp',
values: [message.timestamp],
),
UI.metaCard(
context: context,
icon: FontAwesomeIcons.solidUser,
title: 'User',
values: [user?.userID ?? '...', user?.username ?? ''],
mainAction: () => {/*TODO*/},
),
UI.metaCard(
context: context,
icon: FontAwesomeIcons.solidBolt,
title: 'Priority',
values: [_prettyPrintPriority(message.priority)],
mainAction: () => {/*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
];
}
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
}