import 'package:flutter/material.dart'; import 'package:frontend/provider/quote_provider.dart'; import 'package:frontend/model/quote.dart'; import 'package:frontend/provider/theme_provider.dart'; import 'package:frontend/values/strings.dart'; import 'package:provider/provider.dart'; import 'package:frontend/values/constants.dart'; class ListPage extends StatefulWidget { const ListPage({super.key}); @override State createState() => _ListPageState(); } class _ListPageState extends State { @override Widget build(BuildContext context) { final provider = context.watch(); final themeProvider = context.watch(); return RefreshIndicator( onRefresh: () async { await provider.readQuotes(); }, child: _bodyBuilder(context, provider, themeProvider), ); } Widget _bodyBuilder(BuildContext context, QuoteProvider provider, ThemeProvider themeProvider) { if (provider.loading) { return Center(child: CircularProgressIndicator()); } if (provider.error != null) { return Center(child: Text(provider.error!)); } final quoteCount = provider.filteredQuotes.length; return Column( children: [ Center( child: Padding( padding: const EdgeInsets.only( top: Constants.edgeInset*2 ), child: Text( '${Strings.allQuotesCount} – $quoteCount', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), ), ), filterBar(context, provider, themeProvider), Expanded( child: ListView.builder( padding: EdgeInsets.all(Constants.edgeInset/2), itemCount: provider.filteredQuotes.length, itemBuilder: (context, index) { final quote = provider.filteredQuotes[index]; return ListTile( contentPadding: EdgeInsets.only( bottom: Constants.edgeInset, left: Constants.edgeInset, right: Constants.edgeInset, ), title: Text(quote.body), subtitle: Text("– ${quote.author}"), trailing: PopupMenuButton( icon: const Icon(Icons.more_vert), onSelected: (value) { if (value == 'update') { // open a dialog window that allows for mods _showUpdateDialog(context, provider, quote); } else if (value == 'delete') { // open a confirmation dialog window for deletion _showDeleteDialog(context, provider, quote); } }, itemBuilder: (context) => [ const PopupMenuItem( value: 'update', child: Row( children: [ Icon(Icons.edit), SizedBox(width: 10), Text(Strings.updateQuote), ], ), ), const PopupMenuItem( value: 'delete', child: Row( children: [ Icon(Icons.delete), SizedBox(width: 10), Text(Strings.deleteQuote), ], ), ), ], ) ); }, ), ), ], ); } void _showUpdateDialog(BuildContext context, QuoteProvider provider, Quote quote) { final bodyController = TextEditingController(text: quote.body); final authorController = TextEditingController(text: quote.author); showDialog( context: context, builder: (context) { return AlertDialog( title: Text(Strings.updateQuote), content: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( autofocus: true, decoration: InputDecoration( label: Text(Strings.quote) ), controller: bodyController, ), TextField( controller: authorController, decoration: InputDecoration( label: Text(Strings.author), suffixIcon: IconButton( icon: Icon(Icons.arrow_drop_down), onPressed: () { showModalBottomSheet( context: context, builder: (context) { var authors = provider.authors.toList()..sort(); return SizedBox( height: 300, child: ListView.builder( padding: const EdgeInsets.all(Constants.edgeInset), itemCount: authors.length, itemBuilder: (context, index) { final author = authors[index]; return ListTile( title: Text(author), onTap: () { authorController.text = author; Navigator.pop(context); }, ); }, ), ); }, ); }, ), ), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text(Strings.cancel), ), ElevatedButton( onPressed: () { final updatedQuote = Quote( id: quote.id, body: bodyController.text, author: authorController.text, ); Navigator.pop(context); provider.updateQuote(updatedQuote); }, child: const Text(Strings.save), ), ], ); }, ); } void _showDeleteDialog(BuildContext context, QuoteProvider provider, Quote quote) { showDialog( context: context, builder: (context) { return AlertDialog( title: const Text('${Strings.deleteQuote}?'), content: const Text(Strings.noUndo), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text(Strings.cancel), ), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.red, ), onPressed: () { Navigator.pop(context); provider.deleteQuote(quote); }, child: const Text(Strings.delete), ), ], ); }, ); } Widget filterBar(BuildContext context, QuoteProvider provider, ThemeProvider themeProvider) { return Padding( padding: const EdgeInsets.all(Constants.edgeInset), child: Row( children: [ Expanded( child: TextField( controller: provider.filterController, onChanged: (_) => provider.filterQuotes(), decoration: InputDecoration( label: Text( '${Strings.filterQuery} – ${Strings.byAttribute} ${(provider.filterBy == 0) ? Strings.author : Strings.quote}' ), suffixIcon: IconButton( icon: Icon(Icons.cancel), onPressed: () { provider.clearFilterController(); provider.filterQuotes(); }, ), ), ), ), IconButton( icon: Icon(Icons.more_horiz), onPressed: () { _showOptionsBottomSheet(context, provider, themeProvider); }, ), ], ), ); } void _showOptionsBottomSheet(BuildContext context, QuoteProvider provider, ThemeProvider themeProvider) { showModalBottomSheet( context: context, builder: (context) { return Padding( padding: const EdgeInsets.all(Constants.edgeInset), child: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( title: Text(Strings.filterBy), trailing: Icon(Icons.filter_alt), onTap: () { _showFilterOptionsBottomSheet(context, provider); }, ), ListTile( title: Text(Strings.exportSelectedQuotes), trailing: Icon(Icons.import_export), onTap: () async { Navigator.pop(context); final scaffoldMessenger = ScaffoldMessenger.of(context); final success = await provider.exportWithSAF(); // Make sure the context is still valid if (!mounted) return; scaffoldMessenger.showSnackBar( SnackBar( content: Text(success ? Strings.fileExportedSuccessfully + provider.exportDir : "export [x]"), duration: const Duration(seconds: 3), ), ); } ), ListTile( title: Text(Strings.userPrefersDarkMode), trailing: Consumer( builder: (context, themeProvider, _) => Row( mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.only(right: Constants.edgeInset), child: Switch( onChanged: (value) { themeProvider.setDarkMode(value); }, value: themeProvider.isDark, ), ), Icon(Icons.dark_mode), ], ), ), ), ], ), ); }, ); } void _showFilterOptionsBottomSheet( BuildContext context, QuoteProvider provider, ) { showModalBottomSheet( context: context, builder: (context) { return Padding( padding: const EdgeInsets.all(Constants.edgeInset), child: Column( mainAxisSize: MainAxisSize.min, children: [ RadioGroup( groupValue: provider.filterBy, onChanged: (value) { if (value != null) { provider.setFilterBy(value); Navigator.pop(context); } }, child: Column( children: [ ListTile( title: const Text(Strings.author), leading: Radio(value: 0), onTap: () { provider.setFilterBy(0); Navigator.pop(context); }, ), ListTile( title: const Text(Strings.quote), leading: Radio(value: 1), onTap: () { provider.setFilterBy(1); Navigator.pop(context); }, ), ], ), ), ], ) ); }, ); } }