From a4859cf213a0783715f75656cd990bb8b5b81b29 Mon Sep 17 00:00:00 2001 From: lahevodvika Date: Wed, 23 Jul 2025 17:49:48 +0200 Subject: [PATCH 1/7] Added 5 ListTiles and ListView in settings.dart --- lib/main.dart | 21 ++++++++++++++++- lib/settings.dart | 60 +++++++++++++++++++++++++++++++++++++++++++++++ pubspec.lock | 8 +++++++ pubspec.yaml | 1 + 4 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 lib/settings.dart diff --git a/lib/main.dart b/lib/main.dart index b2ee005..b1d3f06 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:stashcard/carddetail.dart'; import 'package:stashcard/cardlist.dart'; import 'package:stashcard/db.dart'; +import 'package:stashcard/settings.dart'; import 'package:url_launcher/url_launcher.dart'; enum SortOptions { byName, byDateCreated, byUsage } @@ -37,11 +38,17 @@ class _StashcardState extends State { bool _isSearching = false; TextEditingController _searchController = TextEditingController(); String searchQuery = ''; + int currentPageIndex = 0; @override Widget build(BuildContext context) { const String title = 'Stashcard'; + final List pages = [ + CardGrid(selectedOption: selectedSort, searchQuery: searchQuery,), + SettingsPage() + ]; + return Scaffold( appBar: AppBar( title: _isSearching ? @@ -150,7 +157,19 @@ class _StashcardState extends State { ); }, ), - body: CardGrid(selectedOption: selectedSort, searchQuery: searchQuery,) + bottomNavigationBar: NavigationBar( + onDestinationSelected: (int index) { + setState(() { + currentPageIndex = index; + }); + }, + selectedIndex: currentPageIndex, + destinations: const [ + NavigationDestination(icon: Icon(Icons.home_outlined), selectedIcon: Icon(Icons.home), label: "Home"), + NavigationDestination(icon: Icon(Icons.settings_outlined), selectedIcon: Icon(Icons.settings), label: "Settings"), + ], + ), + body: pages[currentPageIndex], ); } } diff --git a/lib/settings.dart b/lib/settings.dart new file mode 100644 index 0000000..633cceb --- /dev/null +++ b/lib/settings.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +/* +* Settings +* - color scheme +* - dark/light theme +* - github link +* - app lock +* - copyright +* */ + +class SettingsPage extends StatelessWidget { + SettingsPage({super.key}); + + final githubUrl = "https://github.com/LahevOdVika/Stashcard"; + + Future _launchUrl() async { + final Uri url = Uri.parse(githubUrl); + if (!await launchUrl(url)) { + throw Exception("Could not launch $url"); + } + } + + @override + Widget build(BuildContext context) { + return ListView( + children: [ + ListTile( + leading: const Icon(Icons.color_lens), + title: const Text("App color scheme"), + ), + const Divider(), + ListTile( + leading: const Icon(Icons.brightness_4), + title: const Text("Dark/light theme"), + ), + const Divider(), + ListTile( + leading: const Icon(Icons.lock), + title: const Text("App lock"), + ), + const Divider(), + ListTile( + trailing: TextButton.icon( + onPressed: () { + _launchUrl(); + }, + icon: const Icon(Icons.code), + label: const Text("Open source code"), + ), + ), + const Divider(), + ListTile( + trailing: const Text("Copyright © 2025 LahevOdVika"), + ), + ], + ); + } +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index a74fb90..652fac0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -126,6 +126,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_colorpicker: + dependency: "direct main" + description: + name: flutter_colorpicker + sha256: "969de5f6f9e2a570ac660fb7b501551451ea2a1ab9e2097e89475f60e07816ea" + url: "https://pub.dev" + source: hosted + version: "1.1.0" flutter_launcher_icons: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index de23bc8..625bfd5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,6 +22,7 @@ dependencies: url_launcher: ^6.3.1 rename_app: ^1.6.2 shared_preferences: ^2.5.2 + flutter_colorpicker: ^1.1.0 dev_dependencies: flutter_test: From 3612c0d0e0b263684fbb56e0c3960223258489fc Mon Sep 17 00:00:00 2001 From: lahevodvika Date: Wed, 23 Jul 2025 19:04:45 +0200 Subject: [PATCH 2/7] Added the ability to change app theme and color scheme. Gotta work on persistent settings storage. --- lib/main.dart | 35 +++++++++++------- lib/settings.dart | 82 +++++++++++++++++++++++++++++++++++++++-- lib/theme_provider.dart | 34 +++++++++++++++++ 3 files changed, 135 insertions(+), 16 deletions(-) create mode 100644 lib/theme_provider.dart diff --git a/lib/main.dart b/lib/main.dart index b1d3f06..edabd1e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,31 +1,40 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:stashcard/carddetail.dart'; import 'package:stashcard/cardlist.dart'; import 'package:stashcard/db.dart'; import 'package:stashcard/settings.dart'; +import 'package:stashcard/theme_provider.dart'; import 'package:url_launcher/url_launcher.dart'; enum SortOptions { byName, byDateCreated, byUsage } void main() async { WidgetsFlutterBinding.ensureInitialized(); - ThemeMode themeMode = ThemeMode.system; runApp( - MaterialApp( - debugShowCheckedModeBanner: false, - theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.green), - ), - darkTheme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.green, brightness: Brightness.dark), - brightness: Brightness.dark - ), - home: Stashcard(), - themeMode: themeMode, + ChangeNotifierProvider( + create: (context) => ThemeProvider(), + child: StashcardApp(), ) ); } +class StashcardApp extends StatelessWidget { + const StashcardApp({super.key}); + + @override + Widget build(BuildContext context) { + final themeProvider = Provider.of(context); + + return MaterialApp( + theme: themeProvider.lightTheme, + darkTheme: themeProvider.darkTheme, + themeMode: themeProvider.themeMode, + home: Stashcard(), + ); + } +} + class Stashcard extends StatefulWidget { const Stashcard({super.key}); @@ -68,7 +77,7 @@ class _StashcardState extends State { ) : Text(title), actions: [ - IconButton( + IconButton( onPressed: () { setState(() { _isSearching = !_isSearching; diff --git a/lib/settings.dart b/lib/settings.dart index 633cceb..f591b97 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -1,5 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:flutter_colorpicker/flutter_colorpicker.dart'; +import 'package:stashcard/theme_provider.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:provider/provider.dart'; /* * Settings @@ -10,10 +13,47 @@ import 'package:url_launcher/url_launcher.dart'; * - copyright * */ -class SettingsPage extends StatelessWidget { - SettingsPage({super.key}); +enum AppThemeMode { + system("System"), + light("Light"), + dark("Dark"); + final String displayName; + const AppThemeMode(this.displayName); + + ThemeMode toFlutterThemeMode() { + switch (this) { + case AppThemeMode.system: + return ThemeMode.system; + case AppThemeMode.light: + return ThemeMode.light; + case AppThemeMode.dark: + return ThemeMode.dark; + } + } + + static AppThemeMode fromFlutterThemeMode(ThemeMode flutterMode) { + switch (flutterMode) { + case ThemeMode.system: + return AppThemeMode.system; + case ThemeMode.light: + return AppThemeMode.light; + case ThemeMode.dark: + return AppThemeMode.dark; + } + } +} + +class SettingsPage extends StatefulWidget { + const SettingsPage({super.key}); + + @override + State createState() => _SettingsPageState(); +} + +class _SettingsPageState extends State { final githubUrl = "https://github.com/LahevOdVika/Stashcard"; + final TextEditingController _themeModeController = TextEditingController(); Future _launchUrl() async { final Uri url = Uri.parse(githubUrl); @@ -22,18 +62,54 @@ class SettingsPage extends StatelessWidget { } } + ThemeMode? selectedTheme; + @override Widget build(BuildContext context) { + final themeProvider = Provider.of(context, listen: false); + return ListView( children: [ ListTile( leading: const Icon(Icons.color_lens), title: const Text("App color scheme"), + onTap: () { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text("Pick a color"), + content: BlockPicker( + pickerColor: themeProvider.seedColor, + onColorChanged: (Color color) { + themeProvider.setSeedColor(color); + Navigator.of(context).pop(); + }, + ), + ); + }, + ); + }, ), const Divider(), ListTile( leading: const Icon(Icons.brightness_4), - title: const Text("Dark/light theme"), + title: const Text("App theme"), + trailing: DropdownMenu( + initialSelection: AppThemeMode.fromFlutterThemeMode(themeProvider.themeMode), + controller: _themeModeController, + dropdownMenuEntries: AppThemeMode.values.map>( + (AppThemeMode mode) { + return DropdownMenuEntry(value: mode, label: mode.displayName); + }, + ).toList(), + onSelected: (AppThemeMode? selectedAppMode) { + if (selectedAppMode != null) { + themeProvider.setThemeMode(selectedAppMode.toFlutterThemeMode()); + _themeModeController.text = selectedAppMode.displayName; + } + }, + ), ), const Divider(), ListTile( diff --git a/lib/theme_provider.dart b/lib/theme_provider.dart new file mode 100644 index 0000000..7c34067 --- /dev/null +++ b/lib/theme_provider.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; + +class ThemeProvider with ChangeNotifier { + Color _seedColor = Colors.green; + ThemeMode _mode = ThemeMode.system; + + Color get seedColor => _seedColor; + + ThemeData get lightTheme => ThemeData( + colorScheme: ColorScheme.fromSeed( + seedColor: _seedColor, + brightness: Brightness.light + ), + ); + + ThemeData get darkTheme => ThemeData( + colorScheme: ColorScheme.fromSeed( + seedColor: _seedColor, + brightness: Brightness.dark + ), + ); + + ThemeMode get themeMode => _mode; + + void setSeedColor(Color color) { + _seedColor = color; + notifyListeners(); + } + + void setThemeMode(ThemeMode mode) { + _mode = mode; + notifyListeners(); + } +} \ No newline at end of file From 136fd342340d7d9985899c25d32b3cc7f1ce9b24 Mon Sep 17 00:00:00 2001 From: lahevodvika Date: Thu, 24 Jul 2025 01:39:14 +0200 Subject: [PATCH 3/7] Changed NavigationBar behaviour, settings.dart and home.dart both have their own Scaffold (kill me, it's complicated) --- lib/Views/home.dart | 237 +++++++++++++++++++ lib/Views/settings.dart | 141 ++++++++++++ lib/{ => card}/carddetail.dart | 13 +- lib/{ => card}/cardedit.dart | 4 +- lib/{ => card}/cardlist.dart | 2 +- lib/{ => card}/scanner.dart | 6 +- lib/destination.dart | 49 ++++ lib/main.dart | 292 +++++------------------- lib/{ => providers}/db.dart | 3 +- lib/{ => providers}/theme_provider.dart | 0 lib/settings.dart | 136 ----------- 11 files changed, 499 insertions(+), 384 deletions(-) create mode 100644 lib/Views/home.dart create mode 100644 lib/Views/settings.dart rename lib/{ => card}/carddetail.dart (95%) rename lib/{ => card}/cardedit.dart (95%) rename lib/{ => card}/cardlist.dart (98%) rename lib/{ => card}/scanner.dart (94%) create mode 100644 lib/destination.dart rename lib/{ => providers}/db.dart (99%) rename lib/{ => providers}/theme_provider.dart (100%) delete mode 100644 lib/settings.dart diff --git a/lib/Views/home.dart b/lib/Views/home.dart new file mode 100644 index 0000000..c852549 --- /dev/null +++ b/lib/Views/home.dart @@ -0,0 +1,237 @@ +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../card/carddetail.dart'; +import '../card/cardlist.dart'; +import '../providers/db.dart'; + +enum SortOptions { byName, byDateCreated, byUsage } + +class Home extends StatefulWidget { + const Home({super.key}); + + @override + State createState() => _HomeState(); +} + +class _HomeState extends State { + SortOptions selectedSort = SortOptions.byName; + bool _isSearching = false; + TextEditingController _searchController = TextEditingController(); + String searchQuery = ''; + + @override + Widget build(BuildContext context) { + const String title = 'Stashcard'; + + return Scaffold( + appBar: AppBar( + title: _isSearching ? + TextField( + controller: _searchController, + autofocus: true, + decoration: const InputDecoration( + hintText: 'Search...', + border: InputBorder.none, + hintStyle: TextStyle(color: Colors.white), + ), + onChanged: (value) { + setState(() { + searchQuery = value; + }); + }, + ) + : Text(title), + actions: [ + IconButton( + onPressed: () { + setState(() { + _isSearching = !_isSearching; + if (!_isSearching) { + _searchController.clear(); + searchQuery = ''; + } + }); + }, + icon: Icon(_isSearching ? Icons.close : Icons.search) + ), + IconButton( + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) => Theme( + data: ThemeData.from(colorScheme: ColorScheme.of(context)), + child: AlertDialog( + title: const Text("Donate"), + icon: const Icon(Icons.favorite), + iconColor: Colors.red, + content: Column( + mainAxisSize: MainAxisSize.min, + spacing: 10, + children: [ + const Text( + "I'm a student and I work on this app in my free time. If you like it, you can support development by donating. And if you don't want to donate, that's fine too.", + softWrap: true, + ), + const Text("Enjoy the app!", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),) + ], + ), + actions: [ + FilledButton( + onPressed: () => { + launchUrl(Uri.parse("https://ko-fi.com/lahev")) + }, + child: const Text('Donate'), + ), + OutlinedButton( + onPressed: () => { + Navigator.pop(context) + }, + child: const Text('Close'), + ) + ], + ) + )); + }, + icon: const Icon(Icons.favorite_border), + ), + PopupMenuButton( + initialValue: selectedSort, + onSelected: (SortOptions sort) { + setState(() { + selectedSort = sort; + }); + }, + itemBuilder: (BuildContext context) => >[ + const PopupMenuItem( + value: SortOptions.byName, + child: Text('Sort by name') + ), + const PopupMenuItem( + value: SortOptions.byDateCreated, + child: Text('Sort by date created') + ), + const PopupMenuItem( + value: SortOptions.byUsage, + child: Text('Sort by usage') + ), + ], + ), + ], + ), + floatingActionButton: Builder( + builder: (BuildContext context) { + return FloatingActionButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const CardList()) + ); + }, + child: const Icon(Icons.add), + ); + }, + ), + body: CardGrid(selectedOption: selectedSort), + ); + } +} + +class CardGrid extends StatefulWidget { + + final SortOptions selectedOption; + final String searchQuery; + + const CardGrid({super.key, required this.selectedOption, this.searchQuery = ''}); + + @override + State createState() => _CardGridState(); +} + +class _CardGridState extends State { + late Future> _futureCards; + final db = DatabaseHelper(); + + @override + void initState() { + super.initState(); + _futureCards = _loadCards(); + } + + Future> _loadCards() async { + return await db.getUserCardsSorted(widget.selectedOption); + } + + Future _refreshCards() async { + setState(() { + _futureCards = _loadCards(); + }); + + await _futureCards; + } + + @override + void didUpdateWidget(covariant CardGrid oldWidget) { + super.didUpdateWidget(oldWidget); + + if (widget.selectedOption != oldWidget.selectedOption) { + _refreshCards(); + } + + if (widget.searchQuery != oldWidget.searchQuery) { + _refreshCards(); + } + } + + @override + Widget build(BuildContext context) { + return FutureBuilder>( + future: _futureCards, + builder: (context, snapshot) { + if (snapshot.hasError) { + return Center(child: Text("Chyba: ${snapshot.error}")); + } else if (!snapshot.hasData || snapshot.data!.isEmpty) { + return const Center(child: Text("Žádné karty")); + } + + final userCards = snapshot.data!; + final filteredCards = userCards.where((userCard) { + return widget.searchQuery.isEmpty || userCard.name.toLowerCase().contains(widget.searchQuery.toLowerCase()); + }).toList(); + + return RefreshIndicator( + onRefresh: () => _refreshCards(), + child: GridView.builder( + padding: const EdgeInsets.all(20), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + childAspectRatio: 1.5, + ), + itemCount: filteredCards.length, + itemBuilder: (context, index) { + final userCard = filteredCards[index]; + return GestureDetector( + onTap: () async { + await Navigator.push( + context, + MaterialPageRoute(builder: (context) => CardDetail(cardId: userCard.id,)) + ); + db.incrementUsage(userCard.id!); + _refreshCards(); + }, + child: Card( + elevation: 2, + child: Center( + child: Text(userCard.name), + ), + ), + ); + }, + ), + ); + }, + ); + } +} \ No newline at end of file diff --git a/lib/Views/settings.dart b/lib/Views/settings.dart new file mode 100644 index 0000000..a31d271 --- /dev/null +++ b/lib/Views/settings.dart @@ -0,0 +1,141 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_colorpicker/flutter_colorpicker.dart'; +import 'package:stashcard/providers/theme_provider.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:provider/provider.dart'; + +/* +* Settings +* - color scheme +* - dark/light theme +* - github link +* - app lock +* - copyright +* */ + +enum AppThemeMode { + system("System"), + light("Light"), + dark("Dark"); + + final String displayName; + const AppThemeMode(this.displayName); + + ThemeMode toFlutterThemeMode() { + switch (this) { + case AppThemeMode.system: + return ThemeMode.system; + case AppThemeMode.light: + return ThemeMode.light; + case AppThemeMode.dark: + return ThemeMode.dark; + } + } + + static AppThemeMode fromFlutterThemeMode(ThemeMode flutterMode) { + switch (flutterMode) { + case ThemeMode.system: + return AppThemeMode.system; + case ThemeMode.light: + return AppThemeMode.light; + case ThemeMode.dark: + return AppThemeMode.dark; + } + } +} + +class SettingsPage extends StatefulWidget { + const SettingsPage({super.key}); + + @override + State createState() => _SettingsPageState(); +} + +class _SettingsPageState extends State { + final githubUrl = "https://github.com/LahevOdVika/Stashcard"; + final TextEditingController _themeModeController = TextEditingController(); + + Future _launchUrl() async { + final Uri url = Uri.parse(githubUrl); + if (!await launchUrl(url)) { + throw Exception("Could not launch $url"); + } + } + + ThemeMode? selectedTheme; + + @override + Widget build(BuildContext context) { + final themeProvider = Provider.of(context, listen: false); + + return Scaffold( + appBar: AppBar( + title: const Text("Settings"), + ), + body: ListView( + children: [ + ListTile( + leading: const Icon(Icons.color_lens), + title: const Text("App color scheme"), + onTap: () { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text("Pick a color"), + content: BlockPicker( + pickerColor: themeProvider.seedColor, + onColorChanged: (Color color) { + themeProvider.setSeedColor(color); + Navigator.of(context).pop(); + }, + ), + ); + }, + ); + }, + ), + const Divider(), + ListTile( + leading: const Icon(Icons.brightness_4), + title: const Text("App theme"), + trailing: DropdownMenu( + initialSelection: AppThemeMode.fromFlutterThemeMode(themeProvider.themeMode), + controller: _themeModeController, + dropdownMenuEntries: AppThemeMode.values.map>( + (AppThemeMode mode) { + return DropdownMenuEntry(value: mode, label: mode.displayName); + }, + ).toList(), + onSelected: (AppThemeMode? selectedAppMode) { + if (selectedAppMode != null) { + themeProvider.setThemeMode(selectedAppMode.toFlutterThemeMode()); + _themeModeController.text = selectedAppMode.displayName; + } + }, + ), + ), + const Divider(), + ListTile( + leading: const Icon(Icons.lock), + title: const Text("App lock"), + ), + const Divider(), + ListTile( + trailing: TextButton.icon( + onPressed: () { + _launchUrl(); + }, + icon: const Icon(Icons.code), + label: const Text("Source code"), + ), + ), + const Divider(), + ListTile( + trailing: const Text("Copyright © 2025 LahevOdVika"), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/carddetail.dart b/lib/card/carddetail.dart similarity index 95% rename from lib/carddetail.dart rename to lib/card/carddetail.dart index 365eefd..f5b4daf 100644 --- a/lib/carddetail.dart +++ b/lib/card/carddetail.dart @@ -1,9 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:stashcard/cardedit.dart'; -import 'package:stashcard/db.dart'; -import 'package:stashcard/main.dart'; +import 'package:stashcard/card/cardedit.dart'; +import 'package:stashcard/providers/db.dart'; import 'package:syncfusion_flutter_barcodes/barcodes.dart'; +import '../Views/home.dart'; + enum CardOptions { edit, share, delete } Map symbologies = { @@ -55,7 +56,7 @@ class _CardDetailState extends State { }, child: Scaffold( appBar: AppBar( - title: const Text("Card Detail"), + title: const Text("card Detail"), actions: [ PopupMenuButton( initialValue: selectedOption, @@ -99,7 +100,7 @@ class _CardDetailState extends State { await db.deleteUserCard(widget.cardId!); Navigator.pushAndRemoveUntil( context, - MaterialPageRoute(builder: (context) => Stashcard()), + MaterialPageRoute(builder: (context) => Home()), (route) => false, ); }, @@ -146,7 +147,7 @@ class _CardDetailState extends State { ), ), const SizedBox(height: 30), - Text("Card type: ${card!.symbology}"), + Text("card type: ${card!.symbology}"), ], ), ), diff --git a/lib/cardedit.dart b/lib/card/cardedit.dart similarity index 95% rename from lib/cardedit.dart rename to lib/card/cardedit.dart index a53f483..7c2785c 100644 --- a/lib/cardedit.dart +++ b/lib/card/cardedit.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:stashcard/db.dart'; +import 'package:stashcard/providers/db.dart'; class CardEdit extends StatefulWidget { final UserCard card; @@ -29,7 +29,7 @@ class _CardEditState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text("Edit Card"), + title: Text("Edit card"), ), body: Padding( padding: const EdgeInsets.all(8.0), diff --git a/lib/cardlist.dart b/lib/card/cardlist.dart similarity index 98% rename from lib/cardlist.dart rename to lib/card/cardlist.dart index 66b9b98..7347ea4 100644 --- a/lib/cardlist.dart +++ b/lib/card/cardlist.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:stashcard/scanner.dart'; +import 'package:stashcard/card/scanner.dart'; import 'package:flutter/services.dart' show rootBundle; Future> loadCards(String filter) async { diff --git a/lib/scanner.dart b/lib/card/scanner.dart similarity index 94% rename from lib/scanner.dart rename to lib/card/scanner.dart index 18a3333..c9efbc0 100644 --- a/lib/scanner.dart +++ b/lib/card/scanner.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; -import 'package:stashcard/db.dart'; +import 'package:stashcard/providers/db.dart'; import 'carddetail.dart'; -import 'main.dart'; +import '../Views/home.dart'; class Scanner extends StatefulWidget { const Scanner({super.key, required this.cardName}); @@ -55,7 +55,7 @@ class _Scanner extends State { Navigator.pushAndRemoveUntil( context, - MaterialPageRoute(builder: (context) => const Stashcard()), + MaterialPageRoute(builder: (context) => const Home()), (Route route) => false, ); diff --git a/lib/destination.dart b/lib/destination.dart new file mode 100644 index 0000000..074431f --- /dev/null +++ b/lib/destination.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:stashcard/Views/settings.dart'; + +import 'Views/home.dart'; + +class Destination { + const Destination(this.index, this.title, this.icon, this.selectedIcon); + final int index; + final String title; + final IconData icon; + final IconData selectedIcon; +} + +class DestinationView extends StatefulWidget { + const DestinationView({super.key, required this.destination, required this.navigatorKey}); + + final Destination destination; + final Key navigatorKey; + + @override + State createState() => _DestinationViewState(); +} + +class _DestinationViewState extends State { + @override + Widget build(BuildContext context) { + return Navigator( + key: widget.navigatorKey, + onGenerateRoute: (RouteSettings settings) { + return MaterialPageRoute( + settings: settings, + builder: (BuildContext context) { + switch (settings.name) { + case '/': + if (widget.destination.index == 0) { + return const Home(); + } else if (widget.destination.index == 1) { + return const SettingsPage(); + } + break; + } + assert(false); + return const SizedBox(); + }, + ); + }, + ); + } +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index edabd1e..4e81889 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,13 +1,8 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:stashcard/carddetail.dart'; -import 'package:stashcard/cardlist.dart'; -import 'package:stashcard/db.dart'; -import 'package:stashcard/settings.dart'; -import 'package:stashcard/theme_provider.dart'; -import 'package:url_launcher/url_launcher.dart'; +import 'package:stashcard/providers/theme_provider.dart'; -enum SortOptions { byName, byDateCreated, byUsage } +import 'destination.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -30,254 +25,81 @@ class StashcardApp extends StatelessWidget { theme: themeProvider.lightTheme, darkTheme: themeProvider.darkTheme, themeMode: themeProvider.themeMode, - home: Stashcard(), + home: NavigationHandler(), ); } } -class Stashcard extends StatefulWidget { - const Stashcard({super.key}); +class NavigationHandler extends StatefulWidget { + const NavigationHandler(); @override - State createState() => _StashcardState(); + State createState() => _NavigationHandlerState(); } -class _StashcardState extends State { - SortOptions selectedSort = SortOptions.byName; - bool _isSearching = false; - TextEditingController _searchController = TextEditingController(); - String searchQuery = ''; - int currentPageIndex = 0; +class _NavigationHandlerState extends State { + static const List allDestinations = [ + Destination(0, "Home", Icons.home_outlined, Icons.home), + Destination(1, "Settings", Icons.settings_outlined, Icons.settings), + ]; - @override - Widget build(BuildContext context) { - const String title = 'Stashcard'; - - final List pages = [ - CardGrid(selectedOption: selectedSort, searchQuery: searchQuery,), - SettingsPage() - ]; - - return Scaffold( - appBar: AppBar( - title: _isSearching ? - TextField( - controller: _searchController, - autofocus: true, - decoration: const InputDecoration( - hintText: 'Search...', - border: InputBorder.none, - hintStyle: TextStyle(color: Colors.white), - ), - onChanged: (value) { - setState(() { - searchQuery = value; - }); - }, - ) - : Text(title), - actions: [ - IconButton( - onPressed: () { - setState(() { - _isSearching = !_isSearching; - if (!_isSearching) { - _searchController.clear(); - searchQuery = ''; - } - }); - }, - icon: Icon(_isSearching ? Icons.close : Icons.search) - ), - IconButton( - onPressed: () { - showDialog( - context: context, - builder: (BuildContext context) => Theme( - data: ThemeData.from(colorScheme: ColorScheme.of(context)), - child: AlertDialog( - title: const Text("Donate"), - icon: const Icon(Icons.favorite), - iconColor: Colors.red, - content: Column( - mainAxisSize: MainAxisSize.min, - spacing: 10, - children: [ - const Text( - "I'm a student and I work on this app in my free time. If you like it, you can support development by donating. And if you don't want to donate, that's fine too.", - softWrap: true, - ), - const Text("Enjoy the app!", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),) - ], - ), - actions: [ - FilledButton( - onPressed: () => { - launchUrl(Uri.parse("https://ko-fi.com/lahev")) - }, - child: const Text('Donate'), - ), - OutlinedButton( - onPressed: () => { - Navigator.pop(context) - }, - child: const Text('Close'), - ) - ], - ) - )); - }, - icon: const Icon(Icons.favorite_border), - ), - PopupMenuButton( - initialValue: selectedSort, - onSelected: (SortOptions sort) { - setState(() { - selectedSort = sort; - }); - }, - itemBuilder: (BuildContext context) => >[ - const PopupMenuItem( - value: SortOptions.byName, - child: Text('Sort by name') - ), - const PopupMenuItem( - value: SortOptions.byDateCreated, - child: Text('Sort by date created') - ), - const PopupMenuItem( - value: SortOptions.byUsage, - child: Text('Sort by usage') - ), - ], - ), - ], - ), - floatingActionButton: Builder( - builder: (BuildContext context) { - return FloatingActionButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => const CardList()) - ); - }, - child: const Icon(Icons.add), - ); - }, - ), - bottomNavigationBar: NavigationBar( - onDestinationSelected: (int index) { - setState(() { - currentPageIndex = index; - }); - }, - selectedIndex: currentPageIndex, - destinations: const [ - NavigationDestination(icon: Icon(Icons.home_outlined), selectedIcon: Icon(Icons.home), label: "Home"), - NavigationDestination(icon: Icon(Icons.settings_outlined), selectedIcon: Icon(Icons.settings), label: "Settings"), - ], - ), - body: pages[currentPageIndex], - ); - } -} - -class CardGrid extends StatefulWidget { - - final SortOptions selectedOption; - final String searchQuery; - - const CardGrid({super.key, required this.selectedOption, this.searchQuery = ''}); - - @override - State createState() => _CardGridState(); -} + late final List> navigatorKeys; + late final List destinationViews; + int selectedIndex = 0; -class _CardGridState extends State { - late Future> _futureCards; - final db = DatabaseHelper(); @override void initState() { super.initState(); - _futureCards = _loadCards(); - } - - Future> _loadCards() async { - return await db.getUserCardsSorted(widget.selectedOption); - } - - Future _refreshCards() async { - setState(() { - _futureCards = _loadCards(); - }); - - await _futureCards; - } - - @override - void didUpdateWidget(covariant CardGrid oldWidget) { - super.didUpdateWidget(oldWidget); - - if (widget.selectedOption != oldWidget.selectedOption) { - _refreshCards(); - } - - if (widget.searchQuery != oldWidget.searchQuery) { - _refreshCards(); - } + navigatorKeys = List>.generate(allDestinations.length, (int index) => GlobalKey()); + destinationViews = allDestinations.map((Destination destination) { + return DestinationView( + destination: destination, + navigatorKey: navigatorKeys[destination.index], + ); + }).toList(); } @override Widget build(BuildContext context) { - return FutureBuilder>( - future: _futureCards, - builder: (context, snapshot) { - if (snapshot.hasError) { - return Center(child: Text("Chyba: ${snapshot.error}")); - } else if (!snapshot.hasData || snapshot.data!.isEmpty) { - return const Center(child: Text("Žádné karty")); - } - - final userCards = snapshot.data!; - final filteredCards = userCards.where((userCard) { - return widget.searchQuery.isEmpty || userCard.name.toLowerCase().contains(widget.searchQuery.toLowerCase()); - }).toList(); - - return RefreshIndicator( - onRefresh: () => _refreshCards(), - child: GridView.builder( - padding: const EdgeInsets.all(20), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - crossAxisSpacing: 10, - mainAxisSpacing: 10, - childAspectRatio: 1.5, - ), - itemCount: filteredCards.length, - itemBuilder: (context, index) { - final userCard = filteredCards[index]; - return GestureDetector( - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute(builder: (context) => CardDetail(cardId: userCard.id,)) - ); - db.incrementUsage(userCard.id!); - _refreshCards(); - }, - child: Card( - elevation: 2, - child: Center( - child: Text(userCard.name), - ), - ), - ); + /*TODO: Replace with PopScope*/ + return NavigatorPopHandler( + onPop: () { + final NavigatorState navigator = navigatorKeys[selectedIndex].currentState!; + navigator.pop(); + }, + child: Scaffold( + body: SafeArea( + top: false, + child: Stack( + fit: StackFit.expand, + children: allDestinations.map((Destination destination) { + final int index = destination.index; + final Widget view = destinationViews[index]; + if (index == selectedIndex) { + return Offstage(offstage: false, child: view); + } else { + return Offstage(child: view); + } + }).toList(), + ) + ), + bottomNavigationBar: NavigationBar( + selectedIndex: selectedIndex, + onDestinationSelected: (int index) { + setState(() { + selectedIndex = index; + }); }, + destinations: allDestinations.map((Destination destination) { + return NavigationDestination( + icon: Icon(destination.icon), + selectedIcon: Icon(destination.selectedIcon), + label: destination.title, + ); + }).toList(), ), - ); - }, + ) ); } } \ No newline at end of file diff --git a/lib/db.dart b/lib/providers/db.dart similarity index 99% rename from lib/db.dart rename to lib/providers/db.dart index 223629c..138b175 100644 --- a/lib/db.dart +++ b/lib/providers/db.dart @@ -1,6 +1,7 @@ import 'package:path/path.dart'; import 'package:sqflite/sqflite.dart'; -import 'package:stashcard/main.dart'; + +import '../Views/home.dart'; class UserCard { final int? id; diff --git a/lib/theme_provider.dart b/lib/providers/theme_provider.dart similarity index 100% rename from lib/theme_provider.dart rename to lib/providers/theme_provider.dart diff --git a/lib/settings.dart b/lib/settings.dart deleted file mode 100644 index f591b97..0000000 --- a/lib/settings.dart +++ /dev/null @@ -1,136 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_colorpicker/flutter_colorpicker.dart'; -import 'package:stashcard/theme_provider.dart'; -import 'package:url_launcher/url_launcher.dart'; -import 'package:provider/provider.dart'; - -/* -* Settings -* - color scheme -* - dark/light theme -* - github link -* - app lock -* - copyright -* */ - -enum AppThemeMode { - system("System"), - light("Light"), - dark("Dark"); - - final String displayName; - const AppThemeMode(this.displayName); - - ThemeMode toFlutterThemeMode() { - switch (this) { - case AppThemeMode.system: - return ThemeMode.system; - case AppThemeMode.light: - return ThemeMode.light; - case AppThemeMode.dark: - return ThemeMode.dark; - } - } - - static AppThemeMode fromFlutterThemeMode(ThemeMode flutterMode) { - switch (flutterMode) { - case ThemeMode.system: - return AppThemeMode.system; - case ThemeMode.light: - return AppThemeMode.light; - case ThemeMode.dark: - return AppThemeMode.dark; - } - } -} - -class SettingsPage extends StatefulWidget { - const SettingsPage({super.key}); - - @override - State createState() => _SettingsPageState(); -} - -class _SettingsPageState extends State { - final githubUrl = "https://github.com/LahevOdVika/Stashcard"; - final TextEditingController _themeModeController = TextEditingController(); - - Future _launchUrl() async { - final Uri url = Uri.parse(githubUrl); - if (!await launchUrl(url)) { - throw Exception("Could not launch $url"); - } - } - - ThemeMode? selectedTheme; - - @override - Widget build(BuildContext context) { - final themeProvider = Provider.of(context, listen: false); - - return ListView( - children: [ - ListTile( - leading: const Icon(Icons.color_lens), - title: const Text("App color scheme"), - onTap: () { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text("Pick a color"), - content: BlockPicker( - pickerColor: themeProvider.seedColor, - onColorChanged: (Color color) { - themeProvider.setSeedColor(color); - Navigator.of(context).pop(); - }, - ), - ); - }, - ); - }, - ), - const Divider(), - ListTile( - leading: const Icon(Icons.brightness_4), - title: const Text("App theme"), - trailing: DropdownMenu( - initialSelection: AppThemeMode.fromFlutterThemeMode(themeProvider.themeMode), - controller: _themeModeController, - dropdownMenuEntries: AppThemeMode.values.map>( - (AppThemeMode mode) { - return DropdownMenuEntry(value: mode, label: mode.displayName); - }, - ).toList(), - onSelected: (AppThemeMode? selectedAppMode) { - if (selectedAppMode != null) { - themeProvider.setThemeMode(selectedAppMode.toFlutterThemeMode()); - _themeModeController.text = selectedAppMode.displayName; - } - }, - ), - ), - const Divider(), - ListTile( - leading: const Icon(Icons.lock), - title: const Text("App lock"), - ), - const Divider(), - ListTile( - trailing: TextButton.icon( - onPressed: () { - _launchUrl(); - }, - icon: const Icon(Icons.code), - label: const Text("Open source code"), - ), - ), - const Divider(), - ListTile( - trailing: const Text("Copyright © 2025 LahevOdVika"), - ), - ], - ); - } -} \ No newline at end of file From e2170b6b4484aaa81309a777de8f06a890935601 Mon Sep 17 00:00:00 2001 From: lahevodvika Date: Tue, 29 Jul 2025 13:56:43 +0200 Subject: [PATCH 4/7] Changed navigation technique --- lib/destination.dart | 1 - lib/main.dart | 81 +++++++++++++++----------------------------- 2 files changed, 27 insertions(+), 55 deletions(-) diff --git a/lib/destination.dart b/lib/destination.dart index 074431f..429a905 100644 --- a/lib/destination.dart +++ b/lib/destination.dart @@ -37,7 +37,6 @@ class _DestinationViewState extends State { } else if (widget.destination.index == 1) { return const SettingsPage(); } - break; } assert(false); return const SizedBox(); diff --git a/lib/main.dart b/lib/main.dart index 4e81889..8f1851b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:stashcard/Views/home.dart'; +import 'package:stashcard/Views/settings.dart'; import 'package:stashcard/providers/theme_provider.dart'; import 'destination.dart'; @@ -38,68 +40,39 @@ class NavigationHandler extends StatefulWidget { } class _NavigationHandlerState extends State { - static const List allDestinations = [ - Destination(0, "Home", Icons.home_outlined, Icons.home), - Destination(1, "Settings", Icons.settings_outlined, Icons.settings), + List destinations = [ + const Destination(0, "Home", Icons.home_outlined, Icons.home), + const Destination(1, "Settings", Icons.settings_outlined, Icons.settings), ]; - late final List> navigatorKeys; - late final List destinationViews; - int selectedIndex = 0; + List routes = [ + Home(), + SettingsPage(), + ]; + int _selectedIndex = 0; - @override - void initState() { - super.initState(); - navigatorKeys = List>.generate(allDestinations.length, (int index) => GlobalKey()); - destinationViews = allDestinations.map((Destination destination) { - return DestinationView( - destination: destination, - navigatorKey: navigatorKeys[destination.index], - ); - }).toList(); + void _changeDestination(int index) { + setState(() { + _selectedIndex = index; + }); } @override Widget build(BuildContext context) { - /*TODO: Replace with PopScope*/ - return NavigatorPopHandler( - onPop: () { - final NavigatorState navigator = navigatorKeys[selectedIndex].currentState!; - navigator.pop(); - }, - child: Scaffold( - body: SafeArea( - top: false, - child: Stack( - fit: StackFit.expand, - children: allDestinations.map((Destination destination) { - final int index = destination.index; - final Widget view = destinationViews[index]; - if (index == selectedIndex) { - return Offstage(offstage: false, child: view); - } else { - return Offstage(child: view); - } - }).toList(), - ) - ), - bottomNavigationBar: NavigationBar( - selectedIndex: selectedIndex, - onDestinationSelected: (int index) { - setState(() { - selectedIndex = index; - }); - }, - destinations: allDestinations.map((Destination destination) { - return NavigationDestination( - icon: Icon(destination.icon), - selectedIcon: Icon(destination.selectedIcon), - label: destination.title, - ); - }).toList(), - ), - ) + return Scaffold( + body: routes[_selectedIndex], + bottomNavigationBar: NavigationBar( + selectedIndex: _selectedIndex, + onDestinationSelected: _changeDestination, + destinations: destinations.map((Destination destination) => + NavigationDestination( + icon: Icon(destination.icon), + selectedIcon: Icon(destination.selectedIcon), + label: destination.title, + ) + ).toList(), + ), ); } } \ No newline at end of file From 674cd3987b6e7ba60215328cffd80f364657dc8b Mon Sep 17 00:00:00 2001 From: lahevodvika Date: Tue, 29 Jul 2025 23:40:59 +0200 Subject: [PATCH 5/7] Removed some unnecessary dependencies --- pubspec.lock | 112 --------------------------------------------------- pubspec.yaml | 3 -- 2 files changed, 115 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 652fac0..24819ab 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -89,14 +89,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" - english_words: - dependency: "direct main" - description: - name: english_words - sha256: "6a7ef6473a97bd8571b6b641d006a6e58a7c67e65fb6f3d6d1151cb46b0e983c" - url: "https://pub.dev" - source: hosted - version: "4.0.0" fake_async: dependency: transitive description: @@ -113,14 +105,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" - file: - dependency: transitive - description: - name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" - source: hosted - version: "7.0.1" flutter: dependency: "direct main" description: flutter @@ -256,30 +240,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.dev" - source: hosted - version: "2.2.1" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.dev" - source: hosted - version: "2.3.0" petitparser: dependency: transitive description: @@ -320,70 +280,6 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.2" - rename_app: - dependency: "direct main" - description: - name: rename_app - sha256: "7cb304cea0b3cbcc332eab80b21df3c168f25ee47e59e22f88c52476e8ae3e97" - url: "https://pub.dev" - source: hosted - version: "1.6.2" - shared_preferences: - dependency: "direct main" - description: - name: shared_preferences - sha256: "846849e3e9b68f3ef4b60c60cf4b3e02e9321bc7f4d8c4692cf87ffa82fc8a3a" - url: "https://pub.dev" - source: hosted - version: "2.5.2" - shared_preferences_android: - dependency: transitive - description: - name: shared_preferences_android - sha256: "3ec7210872c4ba945e3244982918e502fa2bfb5230dff6832459ca0e1879b7ad" - url: "https://pub.dev" - source: hosted - version: "2.4.8" - shared_preferences_foundation: - dependency: transitive - description: - name: shared_preferences_foundation - sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" - url: "https://pub.dev" - source: hosted - version: "2.5.4" - shared_preferences_linux: - dependency: transitive - description: - name: shared_preferences_linux - sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - shared_preferences_platform_interface: - dependency: transitive - description: - name: shared_preferences_platform_interface - sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 - url: "https://pub.dev" - source: hosted - version: "2.4.3" - shared_preferences_windows: - dependency: transitive - description: - name: shared_preferences_windows - sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" - url: "https://pub.dev" - source: hosted - version: "2.4.1" sky_engine: dependency: transitive description: flutter @@ -597,14 +493,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.dev" - source: hosted - version: "1.1.0" xml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 625bfd5..42977dc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,7 +12,6 @@ dependencies: flutter: sdk: flutter - english_words: ^4.0.0 provider: ^6.1.2 mobile_scanner: ^6.0.5 sqflite: ^2.4.1 @@ -20,8 +19,6 @@ dependencies: syncfusion_flutter_barcodes: ^28.2.3 flutter_launcher_icons: ^0.14.3 url_launcher: ^6.3.1 - rename_app: ^1.6.2 - shared_preferences: ^2.5.2 flutter_colorpicker: ^1.1.0 dev_dependencies: From 9f0be0eb6f1a2608b0ee5627476e9077341ef38f Mon Sep 17 00:00:00 2001 From: lahevodvika Date: Wed, 30 Jul 2025 00:31:03 +0200 Subject: [PATCH 6/7] Pushed whole bunch of fixes --- lib/Views/home.dart | 46 +++++++++++++++++++------------ lib/Views/settings.dart | 21 +++++++------- lib/card/carddetail.dart | 9 +++--- lib/card/cardedit.dart | 2 +- lib/destination.dart | 39 -------------------------- lib/main.dart | 6 ++-- lib/models/enums.dart | 1 + lib/providers/db.dart | 3 +- lib/providers/theme_provider.dart | 7 +++++ 9 files changed, 56 insertions(+), 78 deletions(-) create mode 100644 lib/models/enums.dart diff --git a/lib/Views/home.dart b/lib/Views/home.dart index c852549..bb32b15 100644 --- a/lib/Views/home.dart +++ b/lib/Views/home.dart @@ -1,11 +1,10 @@ import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:stashcard/card/carddetail.dart'; +import 'package:stashcard/card/cardlist.dart'; +import 'package:stashcard/models/enums.dart'; +import 'package:stashcard/providers/db.dart'; -import '../card/carddetail.dart'; -import '../card/cardlist.dart'; -import '../providers/db.dart'; - -enum SortOptions { byName, byDateCreated, byUsage } class Home extends StatefulWidget { const Home({super.key}); @@ -20,6 +19,13 @@ class _HomeState extends State { TextEditingController _searchController = TextEditingController(); String searchQuery = ''; + + @override + void dispose() { + _searchController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { const String title = 'Stashcard'; @@ -33,7 +39,6 @@ class _HomeState extends State { decoration: const InputDecoration( hintText: 'Search...', border: InputBorder.none, - hintStyle: TextStyle(color: Colors.white), ), onChanged: (value) { setState(() { @@ -78,15 +83,22 @@ class _HomeState extends State { ), actions: [ FilledButton( - onPressed: () => { - launchUrl(Uri.parse("https://ko-fi.com/lahev")) + onPressed: () async { + try { + await launchUrl(Uri.parse("https://ko-fi.com/lahev")); + } catch (e) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Could not open donation link')), + ); + } + } }, child: const Text('Donate'), ), OutlinedButton( - onPressed: () => { - Navigator.pop(context) - }, + onPressed: () => + Navigator.pop(context), child: const Text('Close'), ) ], @@ -132,7 +144,7 @@ class _HomeState extends State { ); }, ), - body: CardGrid(selectedOption: selectedSort), + body: CardGrid(selectedOption: selectedSort, searchQuery: searchQuery,), ); } } @@ -166,8 +178,6 @@ class _CardGridState extends State { setState(() { _futureCards = _loadCards(); }); - - await _futureCards; } @override @@ -189,9 +199,9 @@ class _CardGridState extends State { future: _futureCards, builder: (context, snapshot) { if (snapshot.hasError) { - return Center(child: Text("Chyba: ${snapshot.error}")); + return const Center(child: Text("Error loading cards")); } else if (!snapshot.hasData || snapshot.data!.isEmpty) { - return const Center(child: Text("Žádné karty")); + return const Center(child: Text("No cards found")); } final userCards = snapshot.data!; @@ -218,7 +228,9 @@ class _CardGridState extends State { context, MaterialPageRoute(builder: (context) => CardDetail(cardId: userCard.id,)) ); - db.incrementUsage(userCard.id!); + if (userCard.id != null) { + db.incrementUsage(userCard.id!); + } _refreshCards(); }, child: Card( diff --git a/lib/Views/settings.dart b/lib/Views/settings.dart index a31d271..9e6d9c8 100644 --- a/lib/Views/settings.dart +++ b/lib/Views/settings.dart @@ -4,15 +4,6 @@ import 'package:stashcard/providers/theme_provider.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:provider/provider.dart'; -/* -* Settings -* - color scheme -* - dark/light theme -* - github link -* - app lock -* - copyright -* */ - enum AppThemeMode { system("System"), light("Light"), @@ -58,11 +49,19 @@ class _SettingsPageState extends State { Future _launchUrl() async { final Uri url = Uri.parse(githubUrl); if (!await launchUrl(url)) { - throw Exception("Could not launch $url"); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Could not open $githubUrl'),) + ); + } } } - ThemeMode? selectedTheme; + @override + void dispose() { + _themeModeController.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { diff --git a/lib/card/carddetail.dart b/lib/card/carddetail.dart index f5b4daf..1e46af5 100644 --- a/lib/card/carddetail.dart +++ b/lib/card/carddetail.dart @@ -2,8 +2,7 @@ import 'package:flutter/material.dart'; import 'package:stashcard/card/cardedit.dart'; import 'package:stashcard/providers/db.dart'; import 'package:syncfusion_flutter_barcodes/barcodes.dart'; - -import '../Views/home.dart'; +import 'package:stashcard/Views/home.dart'; enum CardOptions { edit, share, delete } @@ -56,7 +55,7 @@ class _CardDetailState extends State { }, child: Scaffold( appBar: AppBar( - title: const Text("card Detail"), + title: const Text("Card Detail"), actions: [ PopupMenuButton( initialValue: selectedOption, @@ -100,7 +99,7 @@ class _CardDetailState extends State { await db.deleteUserCard(widget.cardId!); Navigator.pushAndRemoveUntil( context, - MaterialPageRoute(builder: (context) => Home()), + MaterialPageRoute(builder: (context) => const Home()), (route) => false, ); }, @@ -147,7 +146,7 @@ class _CardDetailState extends State { ), ), const SizedBox(height: 30), - Text("card type: ${card!.symbology}"), + Text("Card type: ${card!.symbology}"), ], ), ), diff --git a/lib/card/cardedit.dart b/lib/card/cardedit.dart index 7c2785c..ab61039 100644 --- a/lib/card/cardedit.dart +++ b/lib/card/cardedit.dart @@ -29,7 +29,7 @@ class _CardEditState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text("Edit card"), + title: Text("Edit Card"), ), body: Padding( padding: const EdgeInsets.all(8.0), diff --git a/lib/destination.dart b/lib/destination.dart index 429a905..fd30033 100644 --- a/lib/destination.dart +++ b/lib/destination.dart @@ -1,7 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:stashcard/Views/settings.dart'; - -import 'Views/home.dart'; class Destination { const Destination(this.index, this.title, this.icon, this.selectedIcon); @@ -9,40 +6,4 @@ class Destination { final String title; final IconData icon; final IconData selectedIcon; -} - -class DestinationView extends StatefulWidget { - const DestinationView({super.key, required this.destination, required this.navigatorKey}); - - final Destination destination; - final Key navigatorKey; - - @override - State createState() => _DestinationViewState(); -} - -class _DestinationViewState extends State { - @override - Widget build(BuildContext context) { - return Navigator( - key: widget.navigatorKey, - onGenerateRoute: (RouteSettings settings) { - return MaterialPageRoute( - settings: settings, - builder: (BuildContext context) { - switch (settings.name) { - case '/': - if (widget.destination.index == 0) { - return const Home(); - } else if (widget.destination.index == 1) { - return const SettingsPage(); - } - } - assert(false); - return const SizedBox(); - }, - ); - }, - ); - } } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 8f1851b..358dbd0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -27,7 +27,7 @@ class StashcardApp extends StatelessWidget { theme: themeProvider.lightTheme, darkTheme: themeProvider.darkTheme, themeMode: themeProvider.themeMode, - home: NavigationHandler(), + home: const NavigationHandler(), ); } } @@ -46,8 +46,8 @@ class _NavigationHandlerState extends State { ]; List routes = [ - Home(), - SettingsPage(), + const Home(), + const SettingsPage(), ]; int _selectedIndex = 0; diff --git a/lib/models/enums.dart b/lib/models/enums.dart new file mode 100644 index 0000000..9233ba3 --- /dev/null +++ b/lib/models/enums.dart @@ -0,0 +1 @@ +enum SortOptions { byName, byDateCreated, byUsage } diff --git a/lib/providers/db.dart b/lib/providers/db.dart index 138b175..ded975d 100644 --- a/lib/providers/db.dart +++ b/lib/providers/db.dart @@ -1,7 +1,6 @@ import 'package:path/path.dart'; import 'package:sqflite/sqflite.dart'; - -import '../Views/home.dart'; +import 'package:stashcard/models/enums.dart'; class UserCard { final int? id; diff --git a/lib/providers/theme_provider.dart b/lib/providers/theme_provider.dart index 7c34067..0b0c0bd 100644 --- a/lib/providers/theme_provider.dart +++ b/lib/providers/theme_provider.dart @@ -1,7 +1,14 @@ import 'package:flutter/material.dart'; +/// Provides theme configuration for the application. +/// +/// This provider manages the seed color and theme mode (light/dark/system) +/// and generates Material themes based on these settings. class ThemeProvider with ChangeNotifier { + /// Sets the seed color for theme generation and notifies listeners. Color _seedColor = Colors.green; + + ///Sets the theme mode and notifies listeners. ThemeMode _mode = ThemeMode.system; Color get seedColor => _seedColor; From f2476048060bcdba9378e87ca0fbd6c244b9a46e Mon Sep 17 00:00:00 2001 From: lahevodvika Date: Wed, 30 Jul 2025 00:35:03 +0200 Subject: [PATCH 7/7] Removed not implemented ListTile feature --- lib/Views/settings.dart | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/Views/settings.dart b/lib/Views/settings.dart index 9e6d9c8..a1aff05 100644 --- a/lib/Views/settings.dart +++ b/lib/Views/settings.dart @@ -115,11 +115,6 @@ class _SettingsPageState extends State { ), ), const Divider(), - ListTile( - leading: const Icon(Icons.lock), - title: const Text("App lock"), - ), - const Divider(), ListTile( trailing: TextButton.icon( onPressed: () {