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 IdeasView extends ConsumerWidget { const IdeasView({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final AsyncValue localData = ref.watch( localRepositoryProvider, ); return localData.when( data: (LocalDataState data) { final List ideas = data.ideas; final Map peopleById = { for (final PersonProfile person in data.people) person.id: person, }; return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { final bool compact = constraints.maxWidth < 760; return Padding( padding: EdgeInsets.fromLTRB( compact ? 16 : 28, compact ? 16 : 24, compact ? 16 : 28, compact ? 20 : 28, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (compact) Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Ideas', style: Theme.of(context).textTheme.headlineMedium, ), const SizedBox(height: 6), Text( 'Capture gift and event ideas before they slip away.', style: Theme.of(context).textTheme.bodyLarge ?.copyWith(color: AppTheme.textSecondary), ), const SizedBox(height: 12), FilledButton.icon( onPressed: () => _addIdea(context, ref, data.people), icon: const Icon(Icons.lightbulb_outline_rounded), label: const Text('Add Idea'), ), ], ) else Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Ideas', style: Theme.of( context, ).textTheme.headlineMedium, ), const SizedBox(height: 6), Text( 'Capture gift and event ideas before they slip away.', style: Theme.of(context).textTheme.bodyLarge ?.copyWith(color: AppTheme.textSecondary), ), ], ), ), FilledButton.icon( onPressed: () => _addIdea(context, ref, data.people), icon: const Icon(Icons.lightbulb_outline_rounded), label: const Text('Add Idea'), ), ], ), const SizedBox(height: 16), Expanded( child: ListView.separated( itemCount: ideas.length, separatorBuilder: (_, _) => const SizedBox(height: 12), itemBuilder: (BuildContext context, int index) { final RelationshipIdea idea = ideas[index]; final PersonProfile? person = idea.personId == null ? null : peopleById[idea.personId!]; return FrostedCard( child: compact ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ _IdeaTypeTag(type: idea.type), const SizedBox(width: 8), Expanded( child: Text( idea.title, style: Theme.of(context) .textTheme .titleMedium ?.copyWith( decoration: idea.isArchived ? TextDecoration .lineThrough : null, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), ], ), if (idea.details.trim().isNotEmpty) Padding( padding: const EdgeInsets.only(top: 8), child: Text( idea.details, style: Theme.of(context) .textTheme .bodyMedium ?.copyWith( color: AppTheme.textSecondary, ), ), ), const SizedBox(height: 8), Text( person == null ? 'Unassigned' : person.name, style: Theme.of(context) .textTheme .labelLarge ?.copyWith( color: AppTheme.textSecondary, ), ), const SizedBox(height: 8), Wrap( spacing: 6, runSpacing: 6, children: [ OutlinedButton.icon( onPressed: () { ref .read( localRepositoryProvider .notifier, ) .toggleIdeaArchived(idea.id); }, icon: Icon( idea.isArchived ? Icons.unarchive_outlined : Icons.archive_outlined, ), label: Text( idea.isArchived ? 'Unarchive' : 'Archive', ), ), OutlinedButton.icon( onPressed: () => _editIdea( context, ref, data.people, idea, ), icon: const Icon(Icons.edit_rounded), label: const Text('Edit'), ), TextButton.icon( onPressed: () { ref .read( localRepositoryProvider .notifier, ) .deleteIdea(idea.id); }, icon: const Icon( Icons.delete_outline_rounded, ), label: const Text('Delete'), ), ], ), ], ) : Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ _IdeaTypeTag(type: idea.type), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( idea.title, style: Theme.of(context) .textTheme .titleMedium ?.copyWith( decoration: idea.isArchived ? TextDecoration .lineThrough : null, ), ), if (idea.details.trim().isNotEmpty) Padding( padding: const EdgeInsets.only( top: 6, ), child: Text( idea.details, style: Theme.of(context) .textTheme .bodyMedium ?.copyWith( color: AppTheme .textSecondary, ), ), ), const SizedBox(height: 8), Text( person == null ? 'Unassigned' : person.name, style: Theme.of(context) .textTheme .labelLarge ?.copyWith( color: AppTheme.textSecondary, ), ), ], ), ), const SizedBox(width: 8), Column( children: [ IconButton( onPressed: () { ref .read( localRepositoryProvider .notifier, ) .toggleIdeaArchived(idea.id); }, icon: Icon( idea.isArchived ? Icons.unarchive_outlined : Icons.archive_outlined, ), tooltip: idea.isArchived ? 'Unarchive' : 'Archive', ), IconButton( onPressed: () => _editIdea( context, ref, data.people, idea, ), icon: const Icon(Icons.edit_rounded), tooltip: 'Edit', ), IconButton( onPressed: () { ref .read( localRepositoryProvider .notifier, ) .deleteIdea(idea.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 ideas')); }, ); } Future _addIdea( BuildContext context, WidgetRef ref, List people, ) async { final _IdeaDraft? draft = await showDialog<_IdeaDraft>( context: context, builder: (BuildContext context) => _IdeaEditorDialog(title: 'Add Idea', people: people), ); if (draft == null) { return; } await ref .read(localRepositoryProvider.notifier) .addIdea( type: draft.type, title: draft.title, details: draft.details, personId: draft.personId, ); } Future _editIdea( BuildContext context, WidgetRef ref, List people, RelationshipIdea idea, ) async { final _IdeaDraft? draft = await showDialog<_IdeaDraft>( context: context, builder: (BuildContext context) => _IdeaEditorDialog( title: 'Edit Idea', people: people, initial: _IdeaDraft( personId: idea.personId, type: idea.type, title: idea.title, details: idea.details, ), ), ); if (draft == null) { return; } await ref .read(localRepositoryProvider.notifier) .updateIdea( idea.copyWith( personId: draft.personId, type: draft.type, title: draft.title, details: draft.details, ), ); } } class _IdeaTypeTag extends StatelessWidget { const _IdeaTypeTag({required this.type}); final IdeaType type; @override Widget build(BuildContext context) { final (Color bg, Color fg, String label) = switch (type) { IdeaType.gift => (const Color(0xFFEAF7FB), AppTheme.primary, 'GIFT'), IdeaType.event => ( const Color(0xFFEEF7F3), const Color(0xFF1D9C66), 'EVENT', ), }; return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: bg, borderRadius: BorderRadius.circular(99), ), child: Text( label, style: Theme.of(context).textTheme.labelMedium?.copyWith( color: fg, fontWeight: FontWeight.w700, ), ), ); } } class _IdeaEditorDialog extends StatefulWidget { const _IdeaEditorDialog({ required this.title, required this.people, this.initial, }); final String title; final List people; final _IdeaDraft? initial; @override State<_IdeaEditorDialog> createState() => _IdeaEditorDialogState(); } class _IdeaEditorDialogState extends State<_IdeaEditorDialog> { late final TextEditingController _titleController; late final TextEditingController _detailsController; late IdeaType _type; String? _personId; @override void initState() { super.initState(); _titleController = TextEditingController(text: widget.initial?.title ?? ''); _detailsController = TextEditingController( text: widget.initial?.details ?? '', ); _type = widget.initial?.type ?? IdeaType.gift; _personId = widget.initial?.personId; } @override void dispose() { _titleController.dispose(); _detailsController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AlertDialog( title: Text(widget.title), content: SingleChildScrollView( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 430), child: Column( mainAxisSize: MainAxisSize.min, children: [ DropdownButtonFormField( initialValue: _type, items: IdeaType.values .map( (IdeaType type) => DropdownMenuItem( value: type, child: Text(type.name.toUpperCase()), ), ) .toList(growable: false), onChanged: (IdeaType? value) { if (value == null) { return; } setState(() { _type = value; }); }, decoration: const InputDecoration(labelText: 'Type'), ), 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'), ), TextField( controller: _titleController, decoration: const InputDecoration(labelText: 'Title'), ), TextField( controller: _detailsController, maxLines: 3, decoration: const InputDecoration(labelText: 'Details'), ), ], ), ), ), 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( _IdeaDraft( personId: _personId, type: _type, title: title, details: _detailsController.text.trim(), ), ); }, child: const Text('Save'), ), ], ); } } class _IdeaDraft { const _IdeaDraft({ required this.personId, required this.type, required this.title, required this.details, }); final String? personId; final IdeaType type; final String title; final String details; }