feat: replace mail protocol with smolmail and rebuild the UI

This commit is contained in:
randogoth 2026-09-26 22:45:38 +03:00
parent cb24b68069
commit e94e4158cc
93 changed files with 6387 additions and 3479 deletions

View file

@ -1,62 +1,82 @@
import "dart:async";
import "package:auto_route/auto_route.dart";
import "package:flutter/material.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:flash_mail/data/providers/providers.dart";
import "package:flash_mail/presentation/widgets/drawer/app_drawer.dart";
import "package:flash_mail/presentation/widgets/image_banner.dart";
import "package:flash_mail/presentation/widgets/message/message_list.dart";
import "package:flash_mail/presentation/widgets/shimmer/message_shimmer.dart";
import "package:smol_mail/data/providers/providers.dart";
import "package:smol_mail/presentation/routes/app_router.gr.dart";
import "package:smol_mail/presentation/widgets/drawer/app_drawer.dart";
import "package:smol_mail/presentation/widgets/image_banner.dart";
import "package:smol_mail/presentation/widgets/message/message_list.dart";
import "package:smol_mail/shared/utils/snackbar.dart";
import "package:smol_mail/smol/errors.dart";
/// No server push in v1 (SPEC.md §13): new mail arrives when the user taps
/// fetch, which pulls everything and acknowledges what verifies.
@RoutePage()
class InboxScreen extends ConsumerStatefulWidget {
class InboxScreen extends ConsumerWidget {
const InboxScreen({super.key});
@override
ConsumerState<InboxScreen> createState() => _InboxScreenState();
}
class _InboxScreenState extends ConsumerState<InboxScreen> {
Timer? _timer;
@override
void initState() {
super.initState();
_timer = Timer.periodic(
const Duration(seconds: 30),
(timer) => ref.refresh(messagesProvider),
);
Future<void> _fetch(BuildContext context, WidgetRef ref) async {
final client = ref.read(clientProvider);
try {
final summary = await client.fetch();
ref.read(revisionProvider.notifier).bump();
if (context.mounted) {
final tail = summary.rejected.isEmpty
? ""
: "\n${summary.rejected.length} left on the server";
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("${summary.stored} new$tail")),
);
}
} on SmolError catch (err) {
if (context.mounted) {
showErrorSnackBar(context, err.message);
}
}
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
final messages = ref.watch(messagesProvider);
final folder = ref.watch(folderProvider);
return Scaffold(
appBar: AppBar(
title: const Text("Inbox"),
title: Text(folder == "inbox" ? "Inbox" : "Sent"),
actions: [
if (folder == "inbox")
IconButton(
tooltip: "Fetch",
onPressed: () => _fetch(context, ref),
icon: const Icon(Icons.cloud_download),
),
],
),
drawer: const AppDrawer(),
body: messages.when(
data: (data) => MessageList(
messages: data,
onRefresh: () => ref.refresh(messagesProvider.future),
),
error: (err, _) => const ImageBanner(
imgSrc: "assets/images/error.png",
text: "Something went wrong!",
),
loading: () => const MessageShimmer(),
skipLoadingOnRefresh: true,
floatingActionButton: FloatingActionButton(
onPressed: () => AutoRouter.of(context).push(ComposeRoute()),
child: const Icon(Icons.edit),
),
body: messages.isEmpty
? RefreshIndicator(
onRefresh: () => folder == "inbox"
? _fetch(context, ref)
: Future.value(),
child: ListView(
children: const [
ImageBanner(
imgSrc: "assets/images/empty.png",
text: "Nothing here yet!",
),
],
),
)
: MessageList(
onRefresh: () => folder == "inbox"
? _fetch(context, ref)
: Future.value(),
),
);
}
}