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/widgets/primary_button.dart"; import "package:smol_mail/presentation/widgets/small_loading_spinner.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"; /// One tap seals one envelope; the button disables while the send is in /// flight so a second tap cannot seal a second message. @RoutePage() class ComposeScreen extends ConsumerStatefulWidget { final String? to; final String? subject; const ComposeScreen({ super.key, this.to, this.subject, }); @override ConsumerState createState() => _ComposeScreenState(); } class _ComposeScreenState extends ConsumerState { late final toController = TextEditingController(text: widget.to ?? ""); late final subjectController = TextEditingController(text: widget.subject ?? ""); final bodyController = TextEditingController(); bool anonymous = false; bool busy = false; @override void dispose() { toController.dispose(); subjectController.dispose(); bodyController.dispose(); super.dispose(); } Future _send() async { final client = ref.read(clientProvider); setState(() => busy = true); try { final to = await client.send( toController.text, subjectController.text, bodyController.text, anonymous: anonymous, ); ref.read(revisionProvider.notifier).bump(); if (mounted) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text("sent to $to"))); AutoRouter.of(context).pop(); } } on SmolError catch (err) { if (mounted) { showErrorSnackBar(context, err.message); } } finally { if (mounted) setState(() => busy = false); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("Write")), body: SingleChildScrollView( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ TextField( controller: toController, decoration: InputDecoration( labelText: "To", hintText: "user@host[:1961] or smol://user@host/key", filled: true, fillColor: Theme.of(context).extension()!.cardFill, ), ), const SizedBox(height: 15), TextField( controller: subjectController, decoration: InputDecoration( labelText: "Subject", filled: true, fillColor: Theme.of(context).extension()!.cardFill, ), ), const SizedBox(height: 15), TextField( controller: bodyController, maxLines: 12, decoration: InputDecoration( labelText: "Body", filled: true, fillColor: Theme.of(context).extension()!.cardFill, ), ), SwitchListTile( value: anonymous, onChanged: (value) => setState(() => anonymous = value), title: Text( "Anonymous (omit the signed Reply-To)", style: Theme.of(context).textTheme.bodySmall, ), ), const SizedBox(height: 10), Center( child: PrimaryButton( onPressed: busy ? () {} : _send, child: busy ? const SmallLoadingSpinner() : const Text("Send"), ), ), ], ), ), ); } }