From c9d39a7193c38ec4b48ba0a8024e62ea2c7aec67 Mon Sep 17 00:00:00 2001 From: odweta Date: Wed, 1 Apr 2026 14:02:52 +0200 Subject: [PATCH 01/16] update settings.json --- frontend/.vscode/settings.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 frontend/.vscode/settings.json diff --git a/frontend/.vscode/settings.json b/frontend/.vscode/settings.json new file mode 100644 index 0000000..f8acfe8 --- /dev/null +++ b/frontend/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "dart.flutterSdkPath": "/opt/flutter" +} \ No newline at end of file From 51ce826de6fa7ec5e464902f0eeb8f0a08b4014d Mon Sep 17 00:00:00 2001 From: odweta Date: Thu, 2 Apr 2026 19:58:41 +0200 Subject: [PATCH 02/16] added correct sorting of quotes in the list; set up a testing environment --- backend/main.go | 2 ++ frontend/lib/provider/quote_provider.dart | 7 ++++++- frontend/lib/repository/quote_repository.dart | 7 +++---- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/backend/main.go b/backend/main.go index 213b0b6..ea5b665 100644 --- a/backend/main.go +++ b/backend/main.go @@ -3,6 +3,7 @@ package main import ( "net/http" "os" + "fmt" "github.com/go-chi/chi/v5" ) @@ -25,5 +26,6 @@ func main() { port = "8080" } + fmt.Println("started and listening on localhost:" + port) http.ListenAndServe(":"+port, r) } \ No newline at end of file diff --git a/frontend/lib/provider/quote_provider.dart b/frontend/lib/provider/quote_provider.dart index a21cee0..6c9d758 100644 --- a/frontend/lib/provider/quote_provider.dart +++ b/frontend/lib/provider/quote_provider.dart @@ -18,7 +18,7 @@ class QuoteProvider extends ChangeNotifier { List get filteredQuotes { final query = filterController.text.toLowerCase(); - return _quotes.where((quote) { + final filtered = _quotes.where((quote) { switch (filterBy) { case 0: return quote.author.toLowerCase().contains(query); @@ -28,6 +28,11 @@ class QuoteProvider extends ChangeNotifier { return true; } }).toList(); + + // sort descending + filtered.sort((a, b) => b.id.compareTo(a.id)); + + return filtered; } final QuoteRepository repo = QuoteRepository(); diff --git a/frontend/lib/repository/quote_repository.dart b/frontend/lib/repository/quote_repository.dart index 4440277..1996225 100644 --- a/frontend/lib/repository/quote_repository.dart +++ b/frontend/lib/repository/quote_repository.dart @@ -1,13 +1,12 @@ -// talks to the DB itself -// TODO: implement the real one - import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:frontend/model/quote.dart'; class QuoteRepository { - static final String _apiBaseUrl = 'https://hlaskovnik-reimagined-latest.onrender.com'; + //static final String _realApiBaseUrl = 'https://hlaskovnik-reimagined-latest.onrender.com'; + static final String _testApiBaseUrl = 'http://localhost:8080'; + static final String _apiBaseUrl = _testApiBaseUrl; static final String _apiKey = "2ogD5kc4sw3JVZoouQSV0w3LGRjSj5IjVFnAnk7IRCIK0ktONCeoOYinbNLSIHuU"; static final String _quotesEndpoint = '/quotes'; From eae9e34ddfdec7e82b4f3e67428d26cc2abf218a Mon Sep 17 00:00:00 2001 From: odweta Date: Thu, 2 Apr 2026 20:35:09 +0200 Subject: [PATCH 03/16] added first working prototype of flashcards --- frontend/lib/main.dart | 3 +- frontend/lib/pages/flashcards.dart | 49 +++++++++++++++++------ frontend/lib/provider/quote_provider.dart | 26 ++++++++++++ frontend/lib/values/strings.dart | 3 ++ 4 files changed, 68 insertions(+), 13 deletions(-) diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index 39a0d06..560c10f 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -62,7 +62,8 @@ class HlaskovnikFrontendApp extends StatelessWidget { ), useMaterial3: true, ), - themeMode: ThemeMode.system, + //themeMode: ThemeMode.system, // TODO: .dark is just for testing + themeMode: ThemeMode.dark, routerConfig: _router, ); } diff --git a/frontend/lib/pages/flashcards.dart b/frontend/lib/pages/flashcards.dart index f2973c4..17e4f07 100644 --- a/frontend/lib/pages/flashcards.dart +++ b/frontend/lib/pages/flashcards.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:frontend/provider/quote_provider.dart'; -import 'package:frontend/model/quote.dart'; import 'package:provider/provider.dart'; @@ -15,6 +14,8 @@ class FlashcardsPage extends StatelessWidget { Widget build(BuildContext context) { //return const Center(child: Text('flashcards! flip flip')); final provider = Provider.of(context); + if (provider.randomQuoteText == "dummy" || + provider.randomQuoteText == "quote text") { provider.nextRandomQuote(); } // initialize the random quote variable return Card( margin: const EdgeInsets.all(Constants.edgeInset), child: Padding( @@ -22,22 +23,46 @@ class FlashcardsPage extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, children: [ - Text('insert random quote body here'), + Padding( + padding: const EdgeInsets.all(Constants.edgeInset*2), + child: Text(provider.randomQuoteText, + style: TextStyle( + fontSize: 35, + ), + ), + ), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - TextButton( - child: Text('flip around'), - onPressed: () { - // TODO: flip the card - }, + Padding( + padding: const EdgeInsets.all(Constants.edgeInset), + child: TextButton.icon( + onPressed: provider.flipRandomQuote, + icon: Icon(Icons.repeat, + size: 35, + ), + label: Text(Strings.flipFlashcard, + style: TextStyle( + fontSize: 35, + ), + ), + ), ), - TextButton( - child: Text('next card'), - onPressed: () { - // TODO: next random card - }, + Padding( + padding: const EdgeInsets.all(Constants.edgeInset), + child: TextButton.icon( + onPressed: provider.nextRandomQuote, + icon: Icon(Icons.arrow_right_alt, + size: 35, + ), + label: Text(Strings.nextFlashcard, + style: TextStyle( + fontSize: 35, + ), + ), + ), ), ], ), diff --git a/frontend/lib/provider/quote_provider.dart b/frontend/lib/provider/quote_provider.dart index 6c9d758..9d5894e 100644 --- a/frontend/lib/provider/quote_provider.dart +++ b/frontend/lib/provider/quote_provider.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:frontend/model/quote.dart'; import 'package:frontend/repository/quote_repository.dart'; +import 'dart:math'; class QuoteProvider extends ChangeNotifier { List _quotes = []; @@ -44,6 +45,31 @@ class QuoteProvider extends ChangeNotifier { filterController.text = ''; } + Quote randomQuote = Quote( + id: 0, + body: "quote text", + author: "dummy" + ); + bool randomQuoteDeterminator = true; + String get randomQuoteText { + if (randomQuoteDeterminator) { + return randomQuote.body; + } else { + return randomQuote.author; + } + } + + void flipRandomQuote() { + randomQuoteDeterminator = !randomQuoteDeterminator; + notifyListeners(); + } + + void nextRandomQuote() { + final random = Random(); + randomQuote = filteredQuotes[random.nextInt(filteredQuotes.length)]; + notifyListeners(); + } + void filterQuotes() { notifyListeners(); } diff --git a/frontend/lib/values/strings.dart b/frontend/lib/values/strings.dart index 306cd05..57756d0 100644 --- a/frontend/lib/values/strings.dart +++ b/frontend/lib/values/strings.dart @@ -19,6 +19,9 @@ class Strings { static const String quote = "Hláška"; static const String author = "Autor"; + static const String flipFlashcard = "Otočit flashkartu"; + static const String nextFlashcard = "Další flashkarta"; + static const String noUndo = "Tuto akci nelze odčinit."; static const String filterQuery = "Filtrovací požadavek"; static const String allQuotesCount = "Celkový počet hlášek"; From 5aa78e8bcaa461f395052139b7c93d3778b60861 Mon Sep 17 00:00:00 2001 From: odweta Date: Fri, 3 Apr 2026 07:33:53 +0200 Subject: [PATCH 04/16] add log middleware (just for testing) --- backend/main.go | 19 ++++++++++--------- backend/middleware.go | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/backend/main.go b/backend/main.go index ea5b665..ccef420 100644 --- a/backend/main.go +++ b/backend/main.go @@ -1,9 +1,9 @@ package main import ( + "fmt" "net/http" "os" - "fmt" "github.com/go-chi/chi/v5" ) @@ -11,15 +11,16 @@ import ( func main() { connectDB() - r := chi.NewRouter() + router := chi.NewRouter() - r.Use(authMiddleware) + router.Use(logMiddleware) + router.Use(authMiddleware) // routes - r.Get("/quotes", getQuotes) - r.Post("/quotes", createQuote) - r.Put("/quotes/{id}", updateQuote) - r.Delete("/quotes/{id}", deleteQuote) + router.Get("/quotes", getQuotes) + router.Post("/quotes", createQuote) + router.Put("/quotes/{id}", updateQuote) + router.Delete("/quotes/{id}", deleteQuote) port := os.Getenv("PORT") if port == "" { @@ -27,5 +28,5 @@ func main() { } fmt.Println("started and listening on localhost:" + port) - http.ListenAndServe(":"+port, r) -} \ No newline at end of file + http.ListenAndServe(":"+port, router) +} diff --git a/backend/middleware.go b/backend/middleware.go index fbf2d45..83ac4a3 100644 --- a/backend/middleware.go +++ b/backend/middleware.go @@ -1,8 +1,14 @@ package main import ( + "bytes" + "fmt" + "io" "net/http" "os" + "time" + + "crypto/subtle" ) func authMiddleware(next http.Handler) http.Handler { @@ -18,7 +24,7 @@ func authMiddleware(next http.Handler) http.Handler { expectedHeader := "Bearer " + expectedKey - if authHeader != expectedHeader { + if subtle.ConstantTimeCompare([]byte(authHeader), []byte(expectedHeader)) != 1 { http.Error(w, "unauthorized", http.StatusUnauthorized) return } @@ -26,3 +32,32 @@ func authMiddleware(next http.Handler) http.Handler { next.ServeHTTP(w, r) }) } + +func logMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + reqMethod := r.Method + reqUrl := r.URL.String() + reqIp := r.RemoteAddr + reqHeaderContentType := r.Header.Get("Content-Type") + defer r.Body.Close() + bodyBytes, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "[log] Failed to read body", http.StatusBadRequest) + return + } + + fmt.Printf("\n[%s] %s request on %s\n", reqIp, reqMethod, reqUrl) + fmt.Println("Timestamp (UTC): " + time.Now().UTC().String()) + fmt.Println("Header Content-Type: " + reqHeaderContentType) + if len(bodyBytes) == 0 { + fmt.Println("Body: ") + } else { + fmt.Println("Body: " + string(bodyBytes)) + } + + // restore the request body for later + r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) + + next.ServeHTTP(w, r) + }) +} From 5477db71d978d3a50ed3c25c6afd7ff2bfb2152d Mon Sep 17 00:00:00 2001 From: odweta Date: Fri, 3 Apr 2026 07:34:18 +0200 Subject: [PATCH 05/16] add alias for running the local test environment --- backend/run_test_env.sh | 1 + 1 file changed, 1 insertion(+) create mode 100755 backend/run_test_env.sh diff --git a/backend/run_test_env.sh b/backend/run_test_env.sh new file mode 100755 index 0000000..d597967 --- /dev/null +++ b/backend/run_test_env.sh @@ -0,0 +1 @@ +~/.local/test-envs/hlaskovnik-reimagined/activate.sh From 8c4a7afa56fef60bc2d6a3b177a0a663c05e923c Mon Sep 17 00:00:00 2001 From: odweta Date: Fri, 3 Apr 2026 12:13:23 +0200 Subject: [PATCH 06/16] fix filter radio buttons (remove deprecated code) --- frontend/lib/pages/list.dart | 60 ++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/frontend/lib/pages/list.dart b/frontend/lib/pages/list.dart index fa7487a..21e221b 100644 --- a/frontend/lib/pages/list.dart +++ b/frontend/lib/pages/list.dart @@ -296,43 +296,37 @@ class ListPage extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - ListTile( - title: const Text(Strings.author), - leading: Radio( - value: 0, - groupValue: provider.filterBy, - onChanged: (value) { - if (value != null) { - provider.setFilterBy(value); - Navigator.pop(context); - } - }, - ), - onTap: () { - provider.setFilterBy(0); - Navigator.pop(context); + RadioGroup( + groupValue: provider.filterBy, + onChanged: (value) { + if (value != null) { + provider.setFilterBy(value); + Navigator.pop(context); + } }, - ), - - ListTile( - title: const Text(Strings.quote), - leading: Radio( - value: 1, - 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); + }, + ), + ], ), - onTap: () { - provider.setFilterBy(1); - Navigator.pop(context); - }, ), ], - ), + ) ); }, ); From 3a9bc5fe7d768387c252e70fe778f01bd9403611 Mon Sep 17 00:00:00 2001 From: odweta Date: Fri, 3 Apr 2026 13:01:22 +0200 Subject: [PATCH 07/16] added quotes export to file --- frontend/lib/model/quote.dart | 5 + frontend/lib/pages/list.dart | 11 +- frontend/lib/provider/quote_provider.dart | 28 ++++ frontend/lib/values/strings.dart | 1 + frontend/pubspec.lock | 166 +++++++++++++++++++++- frontend/pubspec.yaml | 1 + 6 files changed, 203 insertions(+), 9 deletions(-) diff --git a/frontend/lib/model/quote.dart b/frontend/lib/model/quote.dart index f50c252..7473cd0 100644 --- a/frontend/lib/model/quote.dart +++ b/frontend/lib/model/quote.dart @@ -18,4 +18,9 @@ class Quote{ body: json['body'], ); } + + @override + String toString() { + return "$body - $author"; + } } \ No newline at end of file diff --git a/frontend/lib/pages/list.dart b/frontend/lib/pages/list.dart index 21e221b..407b88f 100644 --- a/frontend/lib/pages/list.dart +++ b/frontend/lib/pages/list.dart @@ -274,8 +274,15 @@ class ListPage extends StatelessWidget { title: Text(Strings.exportSelectedQuotes), trailing: Icon(Icons.import_export), onTap: () { - // TODO: trigger an os dialog FileSave - }, + provider.exportQuotesToFile(); + Navigator.pop(context); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(Strings.fileExportedSuccessfully), + duration: const Duration(seconds: 3), + ), + ); + } ), ], ), diff --git a/frontend/lib/provider/quote_provider.dart b/frontend/lib/provider/quote_provider.dart index 9d5894e..64fe4d6 100644 --- a/frontend/lib/provider/quote_provider.dart +++ b/frontend/lib/provider/quote_provider.dart @@ -1,10 +1,12 @@ // gets all quotes from the database and returns them in a list +import 'package:path_provider/path_provider.dart'; import 'package:flutter/material.dart'; import 'package:frontend/model/quote.dart'; import 'package:frontend/repository/quote_repository.dart'; import 'dart:math'; +import 'dart:io'; class QuoteProvider extends ChangeNotifier { List _quotes = []; @@ -149,4 +151,30 @@ class QuoteProvider extends ChangeNotifier { populateAuthors(); notifyListeners(); } + + + Future _getSaveDirectory() async { + if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { + final path = Platform.isWindows + ? Platform.environment['USERPROFILE'] + : Platform.environment['HOME']; + + return Directory(path!); + } + + // Mobile fallback + return await getApplicationDocumentsDirectory(); + } + + Future exportQuotesToFile() async { + final dir = await _getSaveDirectory(); + + final datetime = DateTime.now().toUtc().toString() + .replaceAll(" ", "T") + .replaceAll(":", "-") + .replaceAll(RegExp(r"\..*"), ""); + + final file = File('${dir.path}/hlasky_$datetime.txt'); + await file.writeAsString("${filteredQuotes.join("\n")}\n"); + } } \ No newline at end of file diff --git a/frontend/lib/values/strings.dart b/frontend/lib/values/strings.dart index 57756d0..2ee56d6 100644 --- a/frontend/lib/values/strings.dart +++ b/frontend/lib/values/strings.dart @@ -22,6 +22,7 @@ class Strings { static const String flipFlashcard = "Otočit flashkartu"; static const String nextFlashcard = "Další flashkarta"; + static const String fileExportedSuccessfully = "Soubor byl úspěšně vyexportován."; static const String noUndo = "Tuto akci nelze odčinit."; static const String filterQuery = "Filtrovací požadavek"; static const String allQuotesCount = "Celkový počet hlášek"; diff --git a/frontend/pubspec.lock b/frontend/pubspec.lock index 8c4f5b3..9b40600 100644 --- a/frontend/pubspec.lock +++ b/frontend/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 url: "https://pub.dev" source: hosted - version: "2.13.0" + version: "2.13.1" boolean_selector: dependency: transitive description: @@ -33,6 +33,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" + url: "https://pub.dev" + source: hosted + version: "1.0.0" collection: dependency: transitive description: @@ -41,14 +49,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" url: "https://pub.dev" source: hosted - version: "1.0.8" + version: "1.0.9" fake_async: dependency: transitive description: @@ -57,6 +73,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" flutter: dependency: "direct main" description: flutter @@ -80,14 +112,30 @@ packages: description: flutter source: sdk version: "0.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" go_router: dependency: "direct main" description: name: go_router - sha256: "7974313e217a7771557add6ff2238acb63f635317c35fa590d348fb238f00896" + sha256: "48fb2f42ad057476fa4b733cb95e9f9ea7b0b010bb349ea491dca7dbdb18ffc4" url: "https://pub.dev" source: hosted - version: "17.1.0" + version: "17.2.0" + hooks: + dependency: transitive + description: + name: hooks + sha256: e79ed1e8e1929bc6ecb6ec85f0cb519c887aa5b423705ded0d0f2d9226def388 + url: "https://pub.dev" + source: hosted + version: "1.0.2" http: dependency: "direct main" description: @@ -168,6 +216,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.17.0" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572" + url: "https://pub.dev" + source: hosted + version: "0.17.6" nested: dependency: transitive description: @@ -176,6 +232,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" + url: "https://pub.dev" + source: hosted + version: "9.3.0" path: dependency: transitive description: @@ -184,6 +248,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "149441ca6e4f38193b2e004c0ca6376a3d11f51fa5a77552d8bd4d2b0c0912ba" + url: "https://pub.dev" + source: hosted + version: "2.2.23" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + 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" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" provider: dependency: "direct main" description: @@ -192,6 +320,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.5+1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" sky_engine: dependency: transitive description: flutter @@ -277,6 +413,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" sdks: dart: ">=3.11.3 <4.0.0" - flutter: ">=3.35.0" + flutter: ">=3.38.4" diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml index 924c57f..962d10f 100644 --- a/frontend/pubspec.yaml +++ b/frontend/pubspec.yaml @@ -37,6 +37,7 @@ dependencies: provider: go_router: http: ^1.2.0 + path_provider: dev_dependencies: flutter_test: From 1c8357165fdaa314c634f18191d02a2e87e38dfd Mon Sep 17 00:00:00 2001 From: odweta Date: Fri, 3 Apr 2026 13:08:47 +0200 Subject: [PATCH 08/16] add gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a440f0e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +backend/app From fbcddec8c9f8c3a1fbbd8d74aa54fae010d9750d Mon Sep 17 00:00:00 2001 From: odweta Date: Fri, 3 Apr 2026 13:20:31 +0200 Subject: [PATCH 09/16] add authorCount variable for mapping how many quotes everybody has --- frontend/lib/provider/quote_provider.dart | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/frontend/lib/provider/quote_provider.dart b/frontend/lib/provider/quote_provider.dart index 64fe4d6..9e7bf04 100644 --- a/frontend/lib/provider/quote_provider.dart +++ b/frontend/lib/provider/quote_provider.dart @@ -38,6 +38,20 @@ class QuoteProvider extends ChangeNotifier { return filtered; } + Map get authorCounts { + final counts = {}; + + for (var quote in filteredQuotes) { + counts.update( + quote.author, + (value) => value + 1, + ifAbsent: () => 1, + ); + } + + return counts; + } + final QuoteRepository repo = QuoteRepository(); int filterBy = 0; // 0 = by author, 1 = by quote From c92a5101311374593a45c73aa9d42a018e59afd0 Mon Sep 17 00:00:00 2001 From: odweta Date: Fri, 3 Apr 2026 13:21:21 +0200 Subject: [PATCH 10/16] removed settings (made redundant) --- frontend/lib/main.dart | 4 ++-- frontend/lib/pages/settings.dart | 22 +++++++++++++++++++++- frontend/lib/values/strings.dart | 1 + 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index 560c10f..1ab8a14 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -13,7 +13,7 @@ import 'pages/list.dart'; import 'pages/add.dart'; import 'pages/flashcards.dart'; import 'pages/leaderboard.dart'; -import 'pages/settings.dart'; +//import 'pages/settings.dart'; void main() { runApp( @@ -38,7 +38,7 @@ class HlaskovnikFrontendApp extends StatelessWidget { GoRoute(path: '/add', builder: (context, state) => AddPage()), GoRoute(path: '/flashcards', builder: (context, state) => FlashcardsPage()), GoRoute(path: '/leaderboard', builder: (context, state) => LeaderboardPage()), - GoRoute(path: '/settings', builder: (context, state) => SettingsPage()), + //GoRoute(path: '/settings', builder: (context, state) => SettingsPage()), ], ), ], diff --git a/frontend/lib/pages/settings.dart b/frontend/lib/pages/settings.dart index 74b01b2..8da43c9 100644 --- a/frontend/lib/pages/settings.dart +++ b/frontend/lib/pages/settings.dart @@ -1,10 +1,30 @@ +// !!! MADE REDUNDANT !!! + import 'package:flutter/material.dart'; +//import 'package:frontend/provider/quote_provider.dart'; + +//import 'package:frontend/values/strings.dart'; +//import 'package:provider/provider.dart'; + +/// settings items +/// 1. import quotes from file + class SettingsPage extends StatelessWidget { const SettingsPage({super.key}); @override Widget build(BuildContext context) { - return const Center(child: Text('let\'s set something up!')); + //final provider = context.watch(); + return const Center( + child: Card( + child: Row( + children: [ + //Text(Strings.importQuotesFromFile), + //IconButton(onPressed: provider.importQuotesFromFile, icon: icon) + ], + ), + ), + ); } } \ No newline at end of file diff --git a/frontend/lib/values/strings.dart b/frontend/lib/values/strings.dart index 2ee56d6..954035d 100644 --- a/frontend/lib/values/strings.dart +++ b/frontend/lib/values/strings.dart @@ -22,6 +22,7 @@ class Strings { static const String flipFlashcard = "Otočit flashkartu"; static const String nextFlashcard = "Další flashkarta"; + //static const String importQuotesFromFile = "Importovat hlášky ze souboru"; static const String fileExportedSuccessfully = "Soubor byl úspěšně vyexportován."; static const String noUndo = "Tuto akci nelze odčinit."; static const String filterQuery = "Filtrovací požadavek"; From 8789146b9a95ee9a9784fe848bf120c72e816cce Mon Sep 17 00:00:00 2001 From: odweta Date: Fri, 3 Apr 2026 13:22:28 +0200 Subject: [PATCH 11/16] remove settings (made redundant) --- frontend/lib/main_scaffold.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/lib/main_scaffold.dart b/frontend/lib/main_scaffold.dart index 2e64928..62b0a08 100644 --- a/frontend/lib/main_scaffold.dart +++ b/frontend/lib/main_scaffold.dart @@ -13,7 +13,7 @@ class MainScaffold extends StatelessWidget { case '/add': return 1; case '/flashcards': return 2; case '/leaderboard': return 3; - case '/settings': return 4; + //case '/settings': return 4; default: return 0; } } @@ -36,7 +36,7 @@ class MainScaffold extends StatelessWidget { case 1: context.go('/add'); break; case 2: context.go('/flashcards'); break; case 3: context.go('/leaderboard'); break; - case 4: context.go('/settings'); break; + //case 4: context.go('/settings'); break; } }, @@ -45,7 +45,7 @@ class MainScaffold extends StatelessWidget { BottomNavigationBarItem(icon: Icon(Icons.add), label: Strings.add), BottomNavigationBarItem(icon: Icon(Icons.repeat), label: Strings.flashcards), BottomNavigationBarItem(icon: Icon(Icons.leaderboard), label: Strings.leaderboard), - BottomNavigationBarItem(icon: Icon(Icons.settings), label: Strings.settings), + //BottomNavigationBarItem(icon: Icon(Icons.settings), label: Strings.settings), ], ), ); From c7533d8e4aa4635b0f19b3d03435e923979ef9bd Mon Sep 17 00:00:00 2001 From: odweta Date: Fri, 3 Apr 2026 13:47:36 +0200 Subject: [PATCH 12/16] added leaderboards (without clickthrough to filtered by author - later) --- frontend/lib/pages/add.dart | 13 +------ frontend/lib/pages/leaderboard.dart | 40 +++++++++++++++++++- frontend/lib/provider/quote_provider.dart | 45 ++++++++++++++++++++++- 3 files changed, 85 insertions(+), 13 deletions(-) diff --git a/frontend/lib/pages/add.dart b/frontend/lib/pages/add.dart index fb8be0e..bfb5e98 100644 --- a/frontend/lib/pages/add.dart +++ b/frontend/lib/pages/add.dart @@ -21,18 +21,9 @@ class AddPage extends StatelessWidget { padding: const EdgeInsets.all(Constants.edgeInset), child: Column( mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( - Strings.createQuote, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - - const SizedBox(height: 12), - TextField( autofocus: true, controller: bodyController, diff --git a/frontend/lib/pages/leaderboard.dart b/frontend/lib/pages/leaderboard.dart index 5b99915..96b2cdf 100644 --- a/frontend/lib/pages/leaderboard.dart +++ b/frontend/lib/pages/leaderboard.dart @@ -1,10 +1,48 @@ import 'package:flutter/material.dart'; +import 'package:frontend/provider/quote_provider.dart'; +import 'package:frontend/values/constants.dart'; +import 'package:provider/provider.dart'; class LeaderboardPage extends StatelessWidget { const LeaderboardPage({super.key}); @override Widget build(BuildContext context) { - return const Center(child: Text('top 3!')); + final provider = context.watch(); + return Center( + child: Column( + children: [ + Expanded( + child: ListView.builder( + padding: EdgeInsets.all(Constants.edgeInset), + itemCount: provider.filteredQuotes.length, + itemBuilder: (context, index) { + return + Card( + color: provider.getLeaderboardEntryBgColor(context, index), + child: ListTile( + contentPadding: EdgeInsets.symmetric( + vertical: 0, + horizontal: Constants.edgeInset, + ), + title: Text(provider.sortedAuthorCounts[index].key, + style: TextStyle( + color: provider.getLeaderboardEntryFgColor(context, index) + ), + ), + trailing: Text(provider.sortedAuthorCounts[index].value.toString(), + style: TextStyle( + fontSize: 16, + color: provider.getLeaderboardEntryFgColor(context, index), + ), + ), + ), + ); + }, + ), + ), + ], + ), + ); } } \ No newline at end of file diff --git a/frontend/lib/provider/quote_provider.dart b/frontend/lib/provider/quote_provider.dart index 9e7bf04..1ade5b5 100644 --- a/frontend/lib/provider/quote_provider.dart +++ b/frontend/lib/provider/quote_provider.dart @@ -38,7 +38,7 @@ class QuoteProvider extends ChangeNotifier { return filtered; } - Map get authorCounts { + Map get _authorCounts { final counts = {}; for (var quote in filteredQuotes) { @@ -52,6 +52,23 @@ class QuoteProvider extends ChangeNotifier { return counts; } + List> get sortedAuthorCounts { + final counts = {}; + + for (var quote in filteredQuotes) { + counts.update( + quote.author, + (value) => value + 1, + ifAbsent: () => 1, + ); + } + + final list = counts.entries.toList() + ..sort((a, b) => b.value.compareTo(a.value)); // descending + + return list; + } + final QuoteRepository repo = QuoteRepository(); int filterBy = 0; // 0 = by author, 1 = by quote @@ -191,4 +208,30 @@ class QuoteProvider extends ChangeNotifier { final file = File('${dir.path}/hlasky_$datetime.txt'); await file.writeAsString("${filteredQuotes.join("\n")}\n"); } + + Color getLeaderboardEntryBgColor(BuildContext context, int index) { + switch (index) { + case 0: + return Colors.yellow.shade300; // 1st place + case 1: + return Colors.grey.shade300; // 2nd place + case 2: + return Colors.brown.shade300; // 3rd place + default: + return Theme.of(context).cardColor; // default for all other entries + } + } + + Color getLeaderboardEntryFgColor(BuildContext context, int index) { + switch (index) { + case 0: + return Colors.black; // 1st place + case 1: + return Colors.black; // 2nd place + case 2: + return Colors.black; // 3rd place + default: + return Colors.white; // default for all other entries + } + } } \ No newline at end of file From bc68eef63e44f9049eb03f54d81271a3340fc204 Mon Sep 17 00:00:00 2001 From: odweta Date: Fri, 3 Apr 2026 13:47:46 +0200 Subject: [PATCH 13/16] update flashcards page --- frontend/lib/pages/flashcards.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/lib/pages/flashcards.dart b/frontend/lib/pages/flashcards.dart index 17e4f07..2302742 100644 --- a/frontend/lib/pages/flashcards.dart +++ b/frontend/lib/pages/flashcards.dart @@ -33,7 +33,7 @@ class FlashcardsPage extends StatelessWidget { ), ), ), - Row( + Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Padding( From 8ba8a009a6ca9a2842bba09287c8c12d2df40d30 Mon Sep 17 00:00:00 2001 From: odweta Date: Fri, 3 Apr 2026 13:52:34 +0200 Subject: [PATCH 14/16] updated flashcards --- frontend/lib/main.dart | 3 +-- frontend/lib/pages/flashcards.dart | 2 +- frontend/lib/provider/quote_provider.dart | 16 +--------------- 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index 1ab8a14..49bedce 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -62,8 +62,7 @@ class HlaskovnikFrontendApp extends StatelessWidget { ), useMaterial3: true, ), - //themeMode: ThemeMode.system, // TODO: .dark is just for testing - themeMode: ThemeMode.dark, + themeMode: ThemeMode.system, routerConfig: _router, ); } diff --git a/frontend/lib/pages/flashcards.dart b/frontend/lib/pages/flashcards.dart index 2302742..703df0c 100644 --- a/frontend/lib/pages/flashcards.dart +++ b/frontend/lib/pages/flashcards.dart @@ -34,7 +34,7 @@ class FlashcardsPage extends StatelessWidget { ), ), Column( - mainAxisAlignment: MainAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.end, children: [ Padding( padding: const EdgeInsets.all(Constants.edgeInset), diff --git a/frontend/lib/provider/quote_provider.dart b/frontend/lib/provider/quote_provider.dart index 1ade5b5..1fe1995 100644 --- a/frontend/lib/provider/quote_provider.dart +++ b/frontend/lib/provider/quote_provider.dart @@ -38,20 +38,6 @@ class QuoteProvider extends ChangeNotifier { return filtered; } - Map get _authorCounts { - final counts = {}; - - for (var quote in filteredQuotes) { - counts.update( - quote.author, - (value) => value + 1, - ifAbsent: () => 1, - ); - } - - return counts; - } - List> get sortedAuthorCounts { final counts = {}; @@ -88,7 +74,7 @@ class QuoteProvider extends ChangeNotifier { if (randomQuoteDeterminator) { return randomQuote.body; } else { - return randomQuote.author; + return " – ${randomQuote.author}"; } } From 1f8526a7dd7b5eac8880d2eff3a71bf96e9fef04 Mon Sep 17 00:00:00 2001 From: odweta Date: Fri, 3 Apr 2026 13:53:39 +0200 Subject: [PATCH 15/16] update leaderboard text fg color --- frontend/lib/provider/quote_provider.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/lib/provider/quote_provider.dart b/frontend/lib/provider/quote_provider.dart index 1fe1995..f9c35b8 100644 --- a/frontend/lib/provider/quote_provider.dart +++ b/frontend/lib/provider/quote_provider.dart @@ -211,13 +211,11 @@ class QuoteProvider extends ChangeNotifier { Color getLeaderboardEntryFgColor(BuildContext context, int index) { switch (index) { case 0: - return Colors.black; // 1st place case 1: - return Colors.black; // 2nd place case 2: - return Colors.black; // 3rd place + return Colors.black; default: - return Colors.white; // default for all other entries + return Theme.of(context).textTheme.bodyMedium!.color!; } } } \ No newline at end of file From 1ec4a6503226645a7d1ec6cac3e6cc39dab14b54 Mon Sep 17 00:00:00 2001 From: odweta Date: Fri, 3 Apr 2026 14:26:57 +0200 Subject: [PATCH 16/16] update dark mode handling --- frontend/lib/main.dart | 19 +++++-- frontend/lib/pages/list.dart | 33 +++++++++-- frontend/lib/provider/theme_provider.dart | 34 +++++++++++ frontend/lib/values/strings.dart | 2 + .../Flutter/GeneratedPluginRegistrant.swift | 2 + frontend/pubspec.lock | 56 +++++++++++++++++++ frontend/pubspec.yaml | 1 + 7 files changed, 136 insertions(+), 11 deletions(-) create mode 100644 frontend/lib/provider/theme_provider.dart diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index 49bedce..95cc867 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:frontend/provider/theme_provider.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; @@ -17,10 +18,17 @@ import 'pages/leaderboard.dart'; void main() { runApp( - ChangeNotifierProvider( - create: (_) => QuoteProvider(), - child: HlaskovnikFrontendApp(), - ), + MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (_) => QuoteProvider() + ), + ChangeNotifierProvider( + create: (_) => ThemeProvider() + ) + ], + child: HlaskovnikFrontendApp() + ) ); } @@ -46,6 +54,7 @@ class HlaskovnikFrontendApp extends StatelessWidget { @override Widget build(BuildContext context) { + final themeProvider = context.watch(); return MaterialApp.router( title: Strings.appName, theme: ThemeData( @@ -62,7 +71,7 @@ class HlaskovnikFrontendApp extends StatelessWidget { ), useMaterial3: true, ), - themeMode: ThemeMode.system, + themeMode: themeProvider.isDark ? ThemeMode.dark : ThemeMode.system, routerConfig: _router, ); } diff --git a/frontend/lib/pages/list.dart b/frontend/lib/pages/list.dart index 407b88f..8a9ba4c 100644 --- a/frontend/lib/pages/list.dart +++ b/frontend/lib/pages/list.dart @@ -2,6 +2,7 @@ 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'; @@ -13,15 +14,16 @@ class ListPage extends StatelessWidget { @override Widget build(BuildContext context) { final provider = context.watch(); + final themeProvider = context.watch(); return RefreshIndicator( onRefresh: () async { await provider.readQuotes(); }, - child: _bodyBuilder(context, provider), + child: _bodyBuilder(context, provider, themeProvider), ); } - Widget _bodyBuilder(BuildContext context, QuoteProvider provider) { + Widget _bodyBuilder(BuildContext context, QuoteProvider provider, ThemeProvider themeProvider) { if (provider.loading) { return Center(child: CircularProgressIndicator()); } @@ -51,7 +53,7 @@ class ListPage extends StatelessWidget { ), ), ), - filterBar(context, provider), + filterBar(context, provider, themeProvider), Expanded( child: ListView.builder( padding: EdgeInsets.all(Constants.edgeInset), @@ -220,7 +222,7 @@ class ListPage extends StatelessWidget { ); } - Widget filterBar(BuildContext context, QuoteProvider provider) { + Widget filterBar(BuildContext context, QuoteProvider provider, ThemeProvider themeProvider) { return Padding( padding: const EdgeInsets.all(Constants.edgeInset), child: Row( @@ -246,7 +248,7 @@ class ListPage extends StatelessWidget { IconButton( icon: Icon(Icons.more_horiz), onPressed: () { - _showOptionsBottomSheet(context, provider); + _showOptionsBottomSheet(context, provider, themeProvider); }, ), ], @@ -254,7 +256,7 @@ class ListPage extends StatelessWidget { ); } - void _showOptionsBottomSheet(BuildContext context, QuoteProvider provider) { + void _showOptionsBottomSheet(BuildContext context, QuoteProvider provider, ThemeProvider themeProvider) { showModalBottomSheet( context: context, builder: (context) { @@ -284,6 +286,25 @@ class ListPage extends StatelessWidget { ); } ), + ListTile( + title: Text(Strings.userPrefersDarkMode), + onTap: () { themeProvider.setDarkMode(!themeProvider.isDark); }, + trailing: 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), + ], + ), + ), ], ), ); diff --git a/frontend/lib/provider/theme_provider.dart b/frontend/lib/provider/theme_provider.dart new file mode 100644 index 0000000..25e8560 --- /dev/null +++ b/frontend/lib/provider/theme_provider.dart @@ -0,0 +1,34 @@ + +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +Future saveDarkMode(bool isDark) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool('darkMode', isDark); +} + +Future loadDarkMode() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getBool('darkMode') ?? false; // default light +} + +class ThemeProvider extends ChangeNotifier { + bool _isDark = false; + + bool get isDark => _isDark; + + ThemeProvider() { + _load(); // load saved value on startup + } + + void _load() async { + _isDark = await loadDarkMode(); + notifyListeners(); + } + + void setDarkMode(bool value) async { + _isDark = value; + notifyListeners(); + await saveDarkMode(value); + } +} \ No newline at end of file diff --git a/frontend/lib/values/strings.dart b/frontend/lib/values/strings.dart index 954035d..c7d8193 100644 --- a/frontend/lib/values/strings.dart +++ b/frontend/lib/values/strings.dart @@ -30,4 +30,6 @@ class Strings { static const String filterBy = "Filtrovat podle"; static const String exportSelectedQuotes = "Exportovat vybrané hlášky"; static const String byAttribute = "podle příznaku"; + + static const String userPrefersDarkMode = "Preferuji tmavý režim"; } \ No newline at end of file diff --git a/frontend/macos/Flutter/GeneratedPluginRegistrant.swift b/frontend/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..724bb2a 100644 --- a/frontend/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/frontend/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,8 @@ import FlutterMacOS import Foundation +import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/frontend/pubspec.lock b/frontend/pubspec.lock index 9b40600..ad637dc 100644 --- a/frontend/pubspec.lock +++ b/frontend/pubspec.lock @@ -328,6 +328,62 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf + url: "https://pub.dev" + source: hosted + version: "2.5.5" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53 + url: "https://pub.dev" + source: hosted + version: "2.4.23" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + 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: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + 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 diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml index 962d10f..ece9de9 100644 --- a/frontend/pubspec.yaml +++ b/frontend/pubspec.yaml @@ -38,6 +38,7 @@ dependencies: go_router: http: ^1.2.0 path_provider: + shared_preferences: ^2.1.1 dev_dependencies: flutter_test: