Merge branch 'dev' into 'master'
Frontend first working version See merge request odweta/hlaskovnik_reimagined!1
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
backend/app
|
||||||
+10
-7
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@@ -10,20 +11,22 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
connectDB()
|
connectDB()
|
||||||
|
|
||||||
r := chi.NewRouter()
|
router := chi.NewRouter()
|
||||||
|
|
||||||
r.Use(authMiddleware)
|
router.Use(logMiddleware)
|
||||||
|
router.Use(authMiddleware)
|
||||||
|
|
||||||
// routes
|
// routes
|
||||||
r.Get("/quotes", getQuotes)
|
router.Get("/quotes", getQuotes)
|
||||||
r.Post("/quotes", createQuote)
|
router.Post("/quotes", createQuote)
|
||||||
r.Put("/quotes/{id}", updateQuote)
|
router.Put("/quotes/{id}", updateQuote)
|
||||||
r.Delete("/quotes/{id}", deleteQuote)
|
router.Delete("/quotes/{id}", deleteQuote)
|
||||||
|
|
||||||
port := os.Getenv("PORT")
|
port := os.Getenv("PORT")
|
||||||
if port == "" {
|
if port == "" {
|
||||||
port = "8080"
|
port = "8080"
|
||||||
}
|
}
|
||||||
|
|
||||||
http.ListenAndServe(":"+port, r)
|
fmt.Println("started and listening on localhost:" + port)
|
||||||
|
http.ListenAndServe(":"+port, router)
|
||||||
}
|
}
|
||||||
+36
-1
@@ -1,8 +1,14 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"crypto/subtle"
|
||||||
)
|
)
|
||||||
|
|
||||||
func authMiddleware(next http.Handler) http.Handler {
|
func authMiddleware(next http.Handler) http.Handler {
|
||||||
@@ -18,7 +24,7 @@ func authMiddleware(next http.Handler) http.Handler {
|
|||||||
|
|
||||||
expectedHeader := "Bearer " + expectedKey
|
expectedHeader := "Bearer " + expectedKey
|
||||||
|
|
||||||
if authHeader != expectedHeader {
|
if subtle.ConstantTimeCompare([]byte(authHeader), []byte(expectedHeader)) != 1 {
|
||||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -26,3 +32,32 @@ func authMiddleware(next http.Handler) http.Handler {
|
|||||||
next.ServeHTTP(w, r)
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Executable
+1
@@ -0,0 +1 @@
|
|||||||
|
~/.local/test-envs/hlaskovnik-reimagined/activate.sh
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"dart.flutterSdkPath": "/opt/flutter"
|
||||||
|
}
|
||||||
+16
-7
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:frontend/provider/theme_provider.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
@@ -13,14 +14,21 @@ import 'pages/list.dart';
|
|||||||
import 'pages/add.dart';
|
import 'pages/add.dart';
|
||||||
import 'pages/flashcards.dart';
|
import 'pages/flashcards.dart';
|
||||||
import 'pages/leaderboard.dart';
|
import 'pages/leaderboard.dart';
|
||||||
import 'pages/settings.dart';
|
//import 'pages/settings.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(
|
runApp(
|
||||||
ChangeNotifierProvider(
|
MultiProvider(
|
||||||
create: (_) => QuoteProvider(),
|
providers: [
|
||||||
child: HlaskovnikFrontendApp(),
|
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: '/add', builder: (context, state) => AddPage()),
|
||||||
GoRoute(path: '/flashcards', builder: (context, state) => FlashcardsPage()),
|
GoRoute(path: '/flashcards', builder: (context, state) => FlashcardsPage()),
|
||||||
GoRoute(path: '/leaderboard', builder: (context, state) => LeaderboardPage()),
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final themeProvider = context.watch<ThemeProvider>();
|
||||||
return MaterialApp.router(
|
return MaterialApp.router(
|
||||||
title: Strings.appName,
|
title: Strings.appName,
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
@@ -62,7 +71,7 @@ class HlaskovnikFrontendApp extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
),
|
),
|
||||||
themeMode: ThemeMode.system,
|
themeMode: themeProvider.isDark ? ThemeMode.dark : ThemeMode.system,
|
||||||
routerConfig: _router,
|
routerConfig: _router,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class MainScaffold extends StatelessWidget {
|
|||||||
case '/add': return 1;
|
case '/add': return 1;
|
||||||
case '/flashcards': return 2;
|
case '/flashcards': return 2;
|
||||||
case '/leaderboard': return 3;
|
case '/leaderboard': return 3;
|
||||||
case '/settings': return 4;
|
//case '/settings': return 4;
|
||||||
default: return 0;
|
default: return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,7 +36,7 @@ class MainScaffold extends StatelessWidget {
|
|||||||
case 1: context.go('/add'); break;
|
case 1: context.go('/add'); break;
|
||||||
case 2: context.go('/flashcards'); break;
|
case 2: context.go('/flashcards'); break;
|
||||||
case 3: context.go('/leaderboard'); 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.add), label: Strings.add),
|
||||||
BottomNavigationBarItem(icon: Icon(Icons.repeat), label: Strings.flashcards),
|
BottomNavigationBarItem(icon: Icon(Icons.repeat), label: Strings.flashcards),
|
||||||
BottomNavigationBarItem(icon: Icon(Icons.leaderboard), label: Strings.leaderboard),
|
BottomNavigationBarItem(icon: Icon(Icons.leaderboard), label: Strings.leaderboard),
|
||||||
BottomNavigationBarItem(icon: Icon(Icons.settings), label: Strings.settings),
|
//BottomNavigationBarItem(icon: Icon(Icons.settings), label: Strings.settings),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,4 +18,9 @@ class Quote{
|
|||||||
body: json['body'],
|
body: json['body'],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return "$body - $author";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -21,18 +21,9 @@ class AddPage extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.all(Constants.edgeInset),
|
padding: const EdgeInsets.all(Constants.edgeInset),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
|
||||||
Strings.createQuote,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
|
|
||||||
TextField(
|
TextField(
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
controller: bodyController,
|
controller: bodyController,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:frontend/provider/quote_provider.dart';
|
import 'package:frontend/provider/quote_provider.dart';
|
||||||
import 'package:frontend/model/quote.dart';
|
|
||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
@@ -15,6 +14,8 @@ class FlashcardsPage extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
//return const Center(child: Text('flashcards! flip flip'));
|
//return const Center(child: Text('flashcards! flip flip'));
|
||||||
final provider = Provider.of<QuoteProvider>(context);
|
final provider = Provider.of<QuoteProvider>(context);
|
||||||
|
if (provider.randomQuoteText == "dummy" ||
|
||||||
|
provider.randomQuoteText == "quote text") { provider.nextRandomQuote(); } // initialize the random quote variable
|
||||||
return Card(
|
return Card(
|
||||||
margin: const EdgeInsets.all(Constants.edgeInset),
|
margin: const EdgeInsets.all(Constants.edgeInset),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -22,22 +23,46 @@ class FlashcardsPage extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text('insert random quote body here'),
|
Padding(
|
||||||
Row(
|
padding: const EdgeInsets.all(Constants.edgeInset*2),
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
child: Text(provider.randomQuoteText,
|
||||||
children: [
|
style: TextStyle(
|
||||||
TextButton(
|
fontSize: 35,
|
||||||
child: Text('flip around'),
|
|
||||||
onPressed: () {
|
|
||||||
// TODO: flip the card
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
TextButton(
|
),
|
||||||
child: Text('next card'),
|
),
|
||||||
onPressed: () {
|
Column(
|
||||||
// TODO: next random card
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,10 +1,48 @@
|
|||||||
import 'package:flutter/material.dart';
|
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 {
|
class LeaderboardPage extends StatelessWidget {
|
||||||
const LeaderboardPage({super.key});
|
const LeaderboardPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:frontend/provider/quote_provider.dart';
|
import 'package:frontend/provider/quote_provider.dart';
|
||||||
import 'package:frontend/model/quote.dart';
|
import 'package:frontend/model/quote.dart';
|
||||||
|
import 'package:frontend/provider/theme_provider.dart';
|
||||||
import 'package:frontend/values/strings.dart';
|
import 'package:frontend/values/strings.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
@@ -13,15 +14,16 @@ class ListPage extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final provider = context.watch<QuoteProvider>();
|
final provider = context.watch<QuoteProvider>();
|
||||||
|
final themeProvider = context.watch<ThemeProvider>();
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
await provider.readQuotes();
|
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) {
|
if (provider.loading) {
|
||||||
return Center(child: CircularProgressIndicator());
|
return Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
@@ -51,7 +53,7 @@ class ListPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
filterBar(context, provider),
|
filterBar(context, provider, themeProvider),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
padding: EdgeInsets.all(Constants.edgeInset),
|
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(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(Constants.edgeInset),
|
padding: const EdgeInsets.all(Constants.edgeInset),
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -246,7 +248,7 @@ class ListPage extends StatelessWidget {
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.more_horiz),
|
icon: Icon(Icons.more_horiz),
|
||||||
onPressed: () {
|
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(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
@@ -274,8 +276,34 @@ class ListPage extends StatelessWidget {
|
|||||||
title: Text(Strings.exportSelectedQuotes),
|
title: Text(Strings.exportSelectedQuotes),
|
||||||
trailing: Icon(Icons.import_export),
|
trailing: Icon(Icons.import_export),
|
||||||
onTap: () {
|
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(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
RadioGroup<int>(
|
||||||
title: const Text(Strings.author),
|
groupValue: provider.filterBy,
|
||||||
leading: Radio<int>(
|
onChanged: (value) {
|
||||||
value: 0,
|
if (value != null) {
|
||||||
groupValue: provider.filterBy,
|
provider.setFilterBy(value);
|
||||||
onChanged: (value) {
|
Navigator.pop(context);
|
||||||
if (value != null) {
|
}
|
||||||
provider.setFilterBy(value);
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
provider.setFilterBy(0);
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
},
|
||||||
),
|
child: Column(
|
||||||
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
title: const Text(Strings.quote),
|
title: const Text(Strings.author),
|
||||||
leading: Radio<int>(
|
leading: Radio<int>(value: 0),
|
||||||
value: 1,
|
onTap: () {
|
||||||
groupValue: provider.filterBy,
|
provider.setFilterBy(0);
|
||||||
onChanged: (value) {
|
Navigator.pop(context);
|
||||||
if (value != null) {
|
},
|
||||||
provider.setFilterBy(value);
|
),
|
||||||
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);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,10 +1,30 @@
|
|||||||
|
// !!! MADE REDUNDANT !!!
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
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 {
|
class SettingsPage extends StatelessWidget {
|
||||||
const SettingsPage({super.key});
|
const SettingsPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
// gets all quotes from the database and returns them in a list
|
// 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:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:frontend/model/quote.dart';
|
import 'package:frontend/model/quote.dart';
|
||||||
import 'package:frontend/repository/quote_repository.dart';
|
import 'package:frontend/repository/quote_repository.dart';
|
||||||
|
import 'dart:math';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
class QuoteProvider extends ChangeNotifier {
|
class QuoteProvider extends ChangeNotifier {
|
||||||
List<Quote> _quotes = [];
|
List<Quote> _quotes = [];
|
||||||
@@ -18,7 +21,7 @@ class QuoteProvider extends ChangeNotifier {
|
|||||||
List<Quote> get filteredQuotes {
|
List<Quote> get filteredQuotes {
|
||||||
final query = filterController.text.toLowerCase();
|
final query = filterController.text.toLowerCase();
|
||||||
|
|
||||||
return _quotes.where((quote) {
|
final filtered = _quotes.where((quote) {
|
||||||
switch (filterBy) {
|
switch (filterBy) {
|
||||||
case 0:
|
case 0:
|
||||||
return quote.author.toLowerCase().contains(query);
|
return quote.author.toLowerCase().contains(query);
|
||||||
@@ -28,6 +31,28 @@ class QuoteProvider extends ChangeNotifier {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}).toList();
|
}).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();
|
final QuoteRepository repo = QuoteRepository();
|
||||||
@@ -39,6 +64,31 @@ class QuoteProvider extends ChangeNotifier {
|
|||||||
filterController.text = '';
|
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() {
|
void filterQuotes() {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
@@ -118,4 +168,54 @@ class QuoteProvider extends ChangeNotifier {
|
|||||||
populateAuthors();
|
populateAuthors();
|
||||||
notifyListeners();
|
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!;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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 'dart:convert';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
import 'package:frontend/model/quote.dart';
|
import 'package:frontend/model/quote.dart';
|
||||||
|
|
||||||
class QuoteRepository {
|
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 _apiKey = "2ogD5kc4sw3JVZoouQSV0w3LGRjSj5IjVFnAnk7IRCIK0ktONCeoOYinbNLSIHuU";
|
||||||
static final String _quotesEndpoint = '/quotes';
|
static final String _quotesEndpoint = '/quotes';
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,17 @@ class Strings {
|
|||||||
static const String quote = "Hláška";
|
static const String quote = "Hláška";
|
||||||
static const String author = "Autor";
|
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 noUndo = "Tuto akci nelze odčinit.";
|
||||||
static const String filterQuery = "Filtrovací požadavek";
|
static const String filterQuery = "Filtrovací požadavek";
|
||||||
static const String allQuotesCount = "Celkový počet hlášek";
|
static const String allQuotesCount = "Celkový počet hlášek";
|
||||||
static const String filterBy = "Filtrovat podle";
|
static const String filterBy = "Filtrovat podle";
|
||||||
static const String exportSelectedQuotes = "Exportovat vybrané hlášky";
|
static const String exportSelectedQuotes = "Exportovat vybrané hlášky";
|
||||||
static const String byAttribute = "podle příznaku";
|
static const String byAttribute = "podle příznaku";
|
||||||
|
|
||||||
|
static const String userPrefersDarkMode = "Preferuji tmavý režim";
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,8 @@
|
|||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import shared_preferences_foundation
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
}
|
}
|
||||||
|
|||||||
+215
-7
@@ -5,10 +5,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: async
|
name: async
|
||||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.13.0"
|
version: "2.13.1"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -33,6 +33,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
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:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -41,14 +49,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.19.1"
|
version: "1.19.1"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.7"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: cupertino_icons
|
name: cupertino_icons
|
||||||
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.8"
|
version: "1.0.9"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -57,6 +73,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.3"
|
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:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -80,14 +112,30 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.3"
|
||||||
go_router:
|
go_router:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: go_router
|
name: go_router
|
||||||
sha256: "7974313e217a7771557add6ff2238acb63f635317c35fa590d348fb238f00896"
|
sha256: "48fb2f42ad057476fa4b733cb95e9f9ea7b0b010bb349ea491dca7dbdb18ffc4"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
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:
|
http:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -168,6 +216,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.0"
|
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:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -176,6 +232,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
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:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -184,6 +248,70 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
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:
|
provider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -192,6 +320,70 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.5+1"
|
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:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -277,6 +469,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
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:
|
sdks:
|
||||||
dart: ">=3.11.3 <4.0.0"
|
dart: ">=3.11.3 <4.0.0"
|
||||||
flutter: ">=3.35.0"
|
flutter: ">=3.38.4"
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ dependencies:
|
|||||||
provider:
|
provider:
|
||||||
go_router:
|
go_router:
|
||||||
http: ^1.2.0
|
http: ^1.2.0
|
||||||
|
path_provider:
|
||||||
|
shared_preferences: ^2.1.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user