import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:relationship_saver/core/config/app_theme.dart'; import 'package:relationship_saver/features/local/local_models.dart'; import 'package:relationship_saver/features/local/local_repository.dart'; import 'package:relationship_saver/features/shared/frosted_card.dart'; class RemindersView extends ConsumerWidget { const RemindersView({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final AsyncValue localData = ref.watch( localRepositoryProvider, ); return localData.when( data: (LocalDataState data) { final List reminders = data.reminders; final Map peopleById = { for (final PersonProfile person in data.people) person.id: person, }; return Padding( padding: const EdgeInsets.fromLTRB(28, 24, 28, 28), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Reminders', style: Theme.of(context).textTheme.headlineMedium, ), const SizedBox(height: 6), Text( 'Set recurring nudges so good intentions become habits.', style: Theme.of(context).textTheme.bodyLarge ?.copyWith(color: AppTheme.textSecondary), ), ], ), ), FilledButton.icon( onPressed: () => _addReminder(context, ref, data.people), icon: const Icon(Icons.alarm_add_rounded), label: const Text('Add Reminder'), ), ], ), const SizedBox(height: 16), Expanded( child: ListView.separated( itemCount: reminders.length, separatorBuilder: (_, _) => const SizedBox(height: 12), itemBuilder: (BuildContext context, int index) { final ReminderRule reminder = reminders[index]; final PersonProfile? person = reminder.personId == null ? null : peopleById[reminder.personId!]; return FrostedCard( child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( reminder.title, style: Theme.of( context, ).textTheme.titleMedium, ), const SizedBox(height: 4), Text( '${_labelForCadence(reminder.cadence)} • ${_formatDateTime(reminder.nextAt)}', style: Theme.of(context).textTheme.bodyMedium ?.copyWith(color: AppTheme.textSecondary), ), const SizedBox(height: 4), Text( person == null ? 'Unassigned' : person.name, style: Theme.of(context).textTheme.labelLarge ?.copyWith(color: AppTheme.textSecondary), ), ], ), ), Switch( value: reminder.enabled, onChanged: (_) { ref .read(localRepositoryProvider.notifier) .toggleReminderEnabled(reminder.id); }, ), IconButton( onPressed: () => _editReminder( context, ref, data.people, reminder, ), icon: const Icon(Icons.edit_rounded), tooltip: 'Edit', ), IconButton( onPressed: () { ref .read(localRepositoryProvider.notifier) .deleteReminder(reminder.id); }, icon: const Icon(Icons.delete_outline_rounded), tooltip: 'Delete', ), ], ), ); }, ), ), ], ), ); }, loading: () => const Center(child: CircularProgressIndicator()), error: (Object error, StackTrace stackTrace) { return const Center(child: Text('Unable to load reminders')); }, ); } Future _addReminder( BuildContext context, WidgetRef ref, List people, ) async { final _ReminderDraft? draft = await showDialog<_ReminderDraft>( context: context, builder: (BuildContext context) => _ReminderEditorDialog(title: 'Add Reminder', people: people), ); if (draft == null) { return; } await ref .read(localRepositoryProvider.notifier) .addReminder( title: draft.title, cadence: draft.cadence, nextAt: draft.nextAt, personId: draft.personId, ); } Future _editReminder( BuildContext context, WidgetRef ref, List people, ReminderRule reminder, ) async { final _ReminderDraft? draft = await showDialog<_ReminderDraft>( context: context, builder: (BuildContext context) => _ReminderEditorDialog( title: 'Edit Reminder', people: people, initial: _ReminderDraft( personId: reminder.personId, title: reminder.title, cadence: reminder.cadence, nextAt: reminder.nextAt, ), ), ); if (draft == null) { return; } await ref .read(localRepositoryProvider.notifier) .updateReminder( reminder.copyWith( personId: draft.personId, title: draft.title, cadence: draft.cadence, nextAt: draft.nextAt, ), ); } } class _ReminderEditorDialog extends StatefulWidget { const _ReminderEditorDialog({ required this.title, required this.people, this.initial, }); final String title; final List people; final _ReminderDraft? initial; @override State<_ReminderEditorDialog> createState() => _ReminderEditorDialogState(); } class _ReminderEditorDialogState extends State<_ReminderEditorDialog> { late final TextEditingController _titleController; late ReminderCadence _cadence; late DateTime _nextAt; String? _personId; @override void initState() { super.initState(); _titleController = TextEditingController(text: widget.initial?.title ?? ''); _cadence = widget.initial?.cadence ?? ReminderCadence.weekly; _nextAt = widget.initial?.nextAt ?? DateTime.now().add(const Duration(days: 2)); _personId = widget.initial?.personId; } @override void dispose() { _titleController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AlertDialog( title: Text(widget.title), content: SizedBox( width: 420, child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: _titleController, decoration: const InputDecoration(labelText: 'Title'), ), DropdownButtonFormField( initialValue: _cadence, items: ReminderCadence.values .map( (ReminderCadence cadence) => DropdownMenuItem( value: cadence, child: Text(_labelForCadence(cadence)), ), ) .toList(growable: false), onChanged: (ReminderCadence? value) { if (value == null) { return; } setState(() { _cadence = value; }); }, decoration: const InputDecoration(labelText: 'Cadence'), ), DropdownButtonFormField( initialValue: _personId, items: >[ const DropdownMenuItem( value: null, child: Text('Unassigned'), ), ...widget.people.map( (PersonProfile person) => DropdownMenuItem( value: person.id, child: Text(person.name), ), ), ], onChanged: (String? value) { setState(() { _personId = value; }); }, decoration: const InputDecoration(labelText: 'Person'), ), const SizedBox(height: 12), Row( children: [ Expanded( child: Text( _formatDateTime(_nextAt), style: Theme.of(context).textTheme.bodyLarge, ), ), OutlinedButton.icon( onPressed: _pickDateTime, icon: const Icon(Icons.schedule_rounded), label: const Text('Pick Time'), ), ], ), ], ), ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Cancel'), ), FilledButton( onPressed: () { final String title = _titleController.text.trim(); if (title.isEmpty) { return; } Navigator.of(context).pop( _ReminderDraft( personId: _personId, title: title, cadence: _cadence, nextAt: _nextAt, ), ); }, child: const Text('Save'), ), ], ); } Future _pickDateTime() async { final DateTime now = DateTime.now(); final DateTime? date = await showDatePicker( context: context, initialDate: _nextAt, firstDate: now.subtract(const Duration(days: 1)), lastDate: now.add(const Duration(days: 3650)), ); if (date == null || !mounted) { return; } final TimeOfDay? time = await showTimePicker( context: context, initialTime: TimeOfDay.fromDateTime(_nextAt), ); if (time == null || !mounted) { return; } setState(() { _nextAt = DateTime( date.year, date.month, date.day, time.hour, time.minute, ); }); } } class _ReminderDraft { const _ReminderDraft({ required this.personId, required this.title, required this.cadence, required this.nextAt, }); final String? personId; final String title; final ReminderCadence cadence; final DateTime nextAt; } String _labelForCadence(ReminderCadence cadence) { return switch (cadence) { ReminderCadence.daily => 'Daily', ReminderCadence.weekly => 'Weekly', ReminderCadence.monthly => 'Monthly', }; } String _formatDateTime(DateTime value) { final String month = value.month.toString().padLeft(2, '0'); final String day = value.day.toString().padLeft(2, '0'); final String hour = value.hour.toString().padLeft(2, '0'); final String minute = value.minute.toString().padLeft(2, '0'); return '$month/$day/${value.year} $hour:$minute'; }