Merge branch 'dev' into 'master'

Frontend first working version

See merge request odweta/hlaskovnik_reimagined!1
This commit is contained in:
odweta
2026-04-03 14:33:35 +02:00
20 changed files with 605 additions and 100 deletions
+1
View File
@@ -0,0 +1 @@
backend/app
+11 -8
View File
@@ -1,6 +1,7 @@
package main
import (
"fmt"
"net/http"
"os"
@@ -10,20 +11,22 @@ 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 == "" {
port = "8080"
}
http.ListenAndServe(":"+port, r)
}
fmt.Println("started and listening on localhost:" + port)
http.ListenAndServe(":"+port, router)
}
+36 -1
View File
@@ -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: <empty>")
} else {
fmt.Println("Body: " + string(bodyBytes))
}
// restore the request body for later
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
next.ServeHTTP(w, r)
})
}
+1
View File
@@ -0,0 +1 @@
~/.local/test-envs/hlaskovnik-reimagined/activate.sh
+3
View File
@@ -0,0 +1,3 @@
{
"dart.flutterSdkPath": "/opt/flutter"
}
+16 -7
View File
@@ -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';
@@ -13,14 +14,21 @@ 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(
ChangeNotifierProvider(
create: (_) => QuoteProvider(),
child: HlaskovnikFrontendApp(),
),
MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => QuoteProvider()
),
ChangeNotifierProvider(
create: (_) => ThemeProvider()
)
],
child: HlaskovnikFrontendApp()
)
);
}
@@ -38,7 +46,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()),
],
),
],
@@ -46,6 +54,7 @@ class HlaskovnikFrontendApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final themeProvider = context.watch<ThemeProvider>();
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,
);
}
+3 -3
View File
@@ -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),
],
),
);
+5
View File
@@ -18,4 +18,9 @@ class Quote{
body: json['body'],
);
}
@override
String toString() {
return "$body - $author";
}
}
+2 -11
View File
@@ -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,
+40 -15
View File
@@ -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<QuoteProvider>(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'),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
child: Text('flip around'),
onPressed: () {
// TODO: flip the card
},
Padding(
padding: const EdgeInsets.all(Constants.edgeInset*2),
child: Text(provider.randomQuoteText,
style: TextStyle(
fontSize: 35,
),
TextButton(
child: Text('next card'),
onPressed: () {
// TODO: next random card
},
),
),
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
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,
),
),
),
),
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,
),
),
),
),
],
),
+39 -1
View File
@@ -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<QuoteProvider>();
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),
),
),
),
);
},
),
),
],
),
);
}
}
+63 -41
View File
@@ -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<QuoteProvider>();
final themeProvider = context.watch<ThemeProvider>();
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) {
@@ -274,8 +276,34 @@ 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),
),
);
}
),
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),
],
),
),
],
),
@@ -296,43 +324,37 @@ class ListPage extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
title: const Text(Strings.author),
leading: Radio<int>(
value: 0,
groupValue: provider.filterBy,
onChanged: (value) {
if (value != null) {
provider.setFilterBy(value);
Navigator.pop(context);
}
},
),
onTap: () {
provider.setFilterBy(0);
Navigator.pop(context);
RadioGroup<int>(
groupValue: provider.filterBy,
onChanged: (value) {
if (value != null) {
provider.setFilterBy(value);
Navigator.pop(context);
}
},
),
ListTile(
title: const Text(Strings.quote),
leading: Radio<int>(
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<int>(value: 0),
onTap: () {
provider.setFilterBy(0);
Navigator.pop(context);
},
),
ListTile(
title: const Text(Strings.quote),
leading: Radio<int>(value: 1),
onTap: () {
provider.setFilterBy(1);
Navigator.pop(context);
},
),
],
),
onTap: () {
provider.setFilterBy(1);
Navigator.pop(context);
},
),
],
),
)
);
},
);
+21 -1
View File
@@ -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<QuoteProvider>();
return const Center(
child: Card(
child: Row(
children: [
//Text(Strings.importQuotesFromFile),
//IconButton(onPressed: provider.importQuotesFromFile, icon: icon)
],
),
),
);
}
}
+101 -1
View File
@@ -1,9 +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<Quote> _quotes = [];
@@ -18,7 +21,7 @@ class QuoteProvider extends ChangeNotifier {
List<Quote> 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 +31,28 @@ class QuoteProvider extends ChangeNotifier {
return true;
}
}).toList();
// sort descending
filtered.sort((a, b) => b.id.compareTo(a.id));
return filtered;
}
List<MapEntry<String, int>> get sortedAuthorCounts {
final counts = <String, int>{};
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();
@@ -39,6 +64,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();
}
@@ -118,4 +168,54 @@ class QuoteProvider extends ChangeNotifier {
populateAuthors();
notifyListeners();
}
Future<Directory> _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<void> 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");
}
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:
case 1:
case 2:
return Colors.black;
default:
return Theme.of(context).textTheme.bodyMedium!.color!;
}
}
}
+34
View File
@@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
Future<void> saveDarkMode(bool isDark) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('darkMode', isDark);
}
Future<bool> 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);
}
}
@@ -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';
+7
View File
@@ -19,10 +19,17 @@ 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 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";
static const String allQuotesCount = "Celkový počet hlášek";
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";
}
@@ -5,6 +5,8 @@
import FlutterMacOS
import Foundation
import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
}
+215 -7
View File
@@ -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,70 @@ 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"
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
@@ -277,6 +469,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"
+2
View File
@@ -37,6 +37,8 @@ dependencies:
provider:
go_router:
http: ^1.2.0
path_provider:
shared_preferences: ^2.1.1
dev_dependencies:
flutter_test: