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/theme/app_colors.dart"; import "package:smol_mail/shared/utils/snackbar.dart"; import "package:smol_mail/smol/errors.dart"; import "package:smol_mail/smol/proto.dart"; /// Contacts: the addresses bound to keys, with the trust badge and the number /// of keys each has replaced — the §8 surface for noticing rotations. @RoutePage() class ContactsScreen extends ConsumerWidget { const ContactsScreen({super.key}); Future _import(BuildContext context, WidgetRef ref) async { final client = ref.read(clientProvider); final controller = TextEditingController(); final text = await showDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: Theme.of(context).extension()!.cardFill, title: const Text("Import contact"), content: TextField( controller: controller, decoration: InputDecoration( hintText: "smol://user@host/key", filled: true, fillColor: Theme.of(context).colorScheme.surface, ), ), actions: [ TextButton( onPressed: () => Navigator.of(ctx).pop(controller.text), child: const Text("Import"), ), ], ), ); if (text == null || text.trim().isEmpty) return; try { client.importContact(text); ref.read(revisionProvider.notifier).bump(); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text("contact imported (verified key)")), ); } } on SmolError catch (err) { if (context.mounted) { showErrorSnackBar(context, err.message); } } } @override Widget build(BuildContext context, WidgetRef ref) { final contacts = ref.watch(contactsProvider); return Scaffold( appBar: AppBar( title: const Text("Contacts"), actions: [ IconButton( tooltip: "Import smol:// address", onPressed: () => _import(context, ref), icon: const Icon(Icons.person_add), ), ], ), body: contacts.isEmpty ? Center( child: Text( "no contacts yet — import a smol:// address, or write to someone", style: Theme.of(context).textTheme.labelSmall, textAlign: TextAlign.center, ), ) : ListView.builder( itemCount: contacts.length, itemBuilder: (ctx, i) { final (address, contact) = contacts[i]; return ListTile( splashColor: Theme.of(context).extension()!.highlight, onTap: () => AutoRouter.of(context) .push(ContactDetailRoute(address: address)), title: Text(address), subtitle: Text( fingerprint(contact.key), style: Theme.of(context).textTheme.labelSmall, ), trailing: Wrap( spacing: 6, children: [ _chip(context, contact.verified ? "verified" : "tofu", ok: contact.verified), if (contact.history.isNotEmpty) _chip(context, "${contact.history.length} previous", ok: false), ], ), ); }, ), ); } Widget _chip(BuildContext context, String text, {required bool ok}) => Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( border: Border.all( color: ok ? Theme.of(context).primaryColor : Theme.of(context).textTheme.labelSmall!.color!, ), borderRadius: BorderRadius.circular(8), ), child: Text( text, style: Theme.of(context).textTheme.labelSmall!.copyWith( color: ok ? Theme.of(context).primaryColor : Theme.of(context).textTheme.labelSmall!.color, ), ), ); }