2026-09-26 22:45:38 +03:00
|
|
|
import "dart:typed_data";
|
|
|
|
|
|
2024-02-17 20:32:01 +05:30
|
|
|
import "package:auto_route/auto_route.dart";
|
|
|
|
|
import "package:flutter/material.dart";
|
|
|
|
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
|
|
|
|
|
2026-09-26 22:45:38 +03:00
|
|
|
import "package:smol_mail/data/providers/providers.dart";
|
|
|
|
|
import "package:smol_mail/presentation/widgets/image_banner.dart";
|
|
|
|
|
import "package:smol_mail/presentation/widgets/message/message_view.dart";
|
|
|
|
|
import "package:smol_mail/presentation/theme/app_colors.dart";
|
|
|
|
|
import "package:smol_mail/shared/utils/snackbar.dart";
|
|
|
|
|
import "package:smol_mail/smol/errors.dart";
|
2024-02-17 20:32:01 +05:30
|
|
|
|
2026-09-26 22:45:38 +03:00
|
|
|
/// Fetched mail is acknowledged off the server during fetch, so deleting here
|
|
|
|
|
/// removes only the local sealed copy.
|
2024-02-17 20:32:01 +05:30
|
|
|
@RoutePage()
|
|
|
|
|
class MessageDetailScreen extends ConsumerWidget {
|
2026-09-26 22:45:38 +03:00
|
|
|
final String folder;
|
2024-02-17 20:32:01 +05:30
|
|
|
final String id;
|
|
|
|
|
|
|
|
|
|
const MessageDetailScreen({
|
|
|
|
|
super.key,
|
2026-09-26 22:45:38 +03:00
|
|
|
required this.folder,
|
2024-02-17 20:32:01 +05:30
|
|
|
required this.id,
|
|
|
|
|
});
|
|
|
|
|
|
2026-09-26 22:45:38 +03:00
|
|
|
Future<void> _delete(BuildContext context, WidgetRef ref) async {
|
|
|
|
|
await ref.read(storeProvider).deleteMessage(folder, id);
|
|
|
|
|
ref.read(revisionProvider.notifier).bump();
|
2024-02-17 20:32:01 +05:30
|
|
|
if (context.mounted) {
|
|
|
|
|
AutoRouter.of(context).pop();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-26 22:45:38 +03:00
|
|
|
Future<void> _nameSender(BuildContext context, WidgetRef ref,
|
|
|
|
|
Uint8List senderKey) async {
|
|
|
|
|
final client = ref.read(clientProvider);
|
|
|
|
|
final controller = TextEditingController();
|
|
|
|
|
final address = await showDialog<String>(
|
|
|
|
|
context: context,
|
|
|
|
|
builder: (ctx) => AlertDialog(
|
|
|
|
|
backgroundColor: Theme.of(context).extension<AppColors>()!.cardFill,
|
|
|
|
|
content: TextField(
|
|
|
|
|
controller: controller,
|
|
|
|
|
decoration: InputDecoration(
|
|
|
|
|
hintText: "user@host or smol://…",
|
|
|
|
|
filled: true,
|
|
|
|
|
fillColor: Theme.of(context).colorScheme.surface,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
actions: [
|
|
|
|
|
TextButton(
|
|
|
|
|
onPressed: () => Navigator.of(ctx).pop(controller.text),
|
|
|
|
|
child: const Text("Save"),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
if (address == null || address.trim().isEmpty) return;
|
|
|
|
|
try {
|
|
|
|
|
await client.nameSender(address, senderKey);
|
|
|
|
|
ref.read(revisionProvider.notifier).bump();
|
|
|
|
|
if (context.mounted) {
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
|
|
|
const SnackBar(content: Text("Address saved for this sender")),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} on SmolError catch (err) {
|
|
|
|
|
if (context.mounted) {
|
|
|
|
|
showErrorSnackBar(context, err.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-17 20:32:01 +05:30
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
2026-09-26 22:45:38 +03:00
|
|
|
final record = ref.watch(storeProvider).getMessage(folder, id);
|
|
|
|
|
if (record == null) {
|
|
|
|
|
return Scaffold(
|
|
|
|
|
appBar: AppBar(),
|
|
|
|
|
body: const ImageBanner(
|
|
|
|
|
imgSrc: "assets/images/error.png",
|
|
|
|
|
text: "Message not found!",
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
final client = ref.read(clientProvider);
|
|
|
|
|
final opened = client.describe(record);
|
|
|
|
|
final isSent = folder == "sent";
|
|
|
|
|
final who = isSent
|
|
|
|
|
? (record.recipient ?? "")
|
|
|
|
|
: (opened.sender == null
|
|
|
|
|
? ""
|
|
|
|
|
: ref.watch(storeProvider).addressForKey(opened.sender!) ?? "");
|
2024-02-17 20:32:01 +05:30
|
|
|
|
|
|
|
|
return Scaffold(
|
|
|
|
|
appBar: AppBar(
|
2026-09-26 22:45:38 +03:00
|
|
|
title: who.isEmpty
|
|
|
|
|
? null
|
|
|
|
|
: Text(
|
|
|
|
|
who,
|
|
|
|
|
overflow: TextOverflow.ellipsis,
|
|
|
|
|
style: TextStyle(
|
|
|
|
|
fontFamily: "Inter",
|
|
|
|
|
fontWeight: FontWeight.w500,
|
|
|
|
|
fontSize: 18,
|
|
|
|
|
color: Theme.of(context).colorScheme.onSurface,
|
|
|
|
|
),
|
|
|
|
|
),
|
2024-02-17 20:32:01 +05:30
|
|
|
actions: [
|
|
|
|
|
IconButton(
|
2026-09-26 22:45:38 +03:00
|
|
|
tooltip: "Delete local copy",
|
|
|
|
|
onPressed: () => _delete(context, ref),
|
|
|
|
|
icon: const Icon(Icons.delete),
|
|
|
|
|
),
|
2024-02-17 20:32:01 +05:30
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
body: SingleChildScrollView(
|
2026-09-26 22:45:38 +03:00
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
|
|
|
|
child: MessageView(
|
|
|
|
|
record: record,
|
|
|
|
|
opened: opened,
|
|
|
|
|
onNameSender: opened.sender == null
|
|
|
|
|
? null
|
|
|
|
|
: (key) => _nameSender(context, ref, key),
|
2024-02-17 20:32:01 +05:30
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|