import "package:auto_route/auto_route.dart"; import "package:flutter/material.dart"; import "package:flutter_riverpod/flutter_riverpod.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/image_banner.dart"; import "package:smol_mail/presentation/widgets/message/message_list.dart"; import "package:smol_mail/presentation/widgets/qr_code_sheet.dart"; import "package:smol_mail/shared/utils/snackbar.dart"; import "package:smol_mail/smol/errors.dart"; const _folders = ["inbox", "requests", "sent"]; /// No server push in v1 (SPEC.md §13): new mail arrives when the user taps /// fetch, which pulls everything and acknowledges what verifies. One FETCH /// call populates both Inbox and Requests (they're tiers of the same /// mailbox), so both — but not the purely local Sent folder — offer fetch. @RoutePage() class InboxScreen extends ConsumerStatefulWidget { const InboxScreen({super.key}); @override ConsumerState createState() => _InboxScreenState(); } class _InboxScreenState extends ConsumerState with SingleTickerProviderStateMixin { late final TabController _tabController; @override void initState() { super.initState(); _tabController = TabController(length: _folders.length, vsync: this) ..addListener(() { // Fires once settled, whether the change came from a tap or a swipe; // only the AppBar's fetch button depends on which tab is active. if (!_tabController.indexIsChanging) setState(() {}); }); } @override void dispose() { _tabController.dispose(); super.dispose(); } Future _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); } } } Widget _folderView(BuildContext context, WidgetRef ref, String folder) { final messages = ref.watch(messagesForFolderProvider(folder)); final canFetch = folder != "sent"; final onRefresh = canFetch ? () => _fetch(context, ref) : () => Future.value(); if (messages.isEmpty) { return RefreshIndicator( onRefresh: onRefresh, child: ListView( children: const [ ImageBanner( imgSrc: "assets/images/empty.png", text: "Nothing here yet!", ), ], ), ); } return MessageList(folder: folder, onRefresh: onRefresh); } @override Widget build(BuildContext context) { final unread = ref.watch(unreadProvider); final requestsUnread = ref.watch(requestsUnreadProvider); final canFetch = _folders[_tabController.index] != "sent"; // The same self-certifying URI the settings share row shows, one tap // closer: what a second device scans to import this identity. final me = ref.watch(identityProvider); final account = ref.watch(accountProvider); final share = (me == null || account == null) ? null : account.uri(me.publicKey); return Scaffold( appBar: AppBar( title: const Text("kirakira"), actions: [ if (share != null) IconButton( tooltip: "Show QR code", onPressed: () => showQrCodeSheet(context, share), icon: const Icon(Icons.qr_code), ), if (canFetch) IconButton( tooltip: "Fetch", onPressed: () => _fetch(context, ref), icon: const Icon(Icons.cloud_download), ), ], bottom: TabBar( controller: _tabController, tabs: [ Tab(text: unread > 0 ? "Inbox ($unread)" : "Inbox"), Tab(text: requestsUnread > 0 ? "Requests ($requestsUnread)" : "Requests"), const Tab(text: "Sent"), ], ), ), floatingActionButton: FloatingActionButton( onPressed: () => AutoRouter.of(context).push(ComposeRoute()), child: const Icon(Icons.edit), ), body: TabBarView( controller: _tabController, children: [ for (final folder in _folders) _folderView(context, ref, folder), ], ), ); } }