Merge branch 'dev' into 'master'

bugfixes no. 1

Closes #7, #6, #2, #3, #1, #5, and #4

See merge request odweta/hlaskovnik_reimagined!2
This commit is contained in:
odweta
2026-04-03 19:18:54 +02:00
17 changed files with 282 additions and 106 deletions
+1 -2
View File
@@ -20,12 +20,11 @@ android {
} }
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "app.odweta.novehlasky" applicationId = "app.odweta.novehlasky"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config. // For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion targetSdk = 33
versionCode = flutter.versionCode versionCode = flutter.versionCode
versionName = flutter.versionName versionName = flutter.versionName
} }
@@ -1,4 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="app.odweta.novehlasky">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application <application
android:label="frontend" android:label="frontend"
android:name="${applicationName}" android:name="${applicationName}"
@@ -1,4 +1,4 @@
package com.example.frontend package app.odweta.novehlasky
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
+4 -2
View File
@@ -16,7 +16,9 @@ import 'pages/flashcards.dart';
import 'pages/leaderboard.dart'; import 'pages/leaderboard.dart';
//import 'pages/settings.dart'; //import 'pages/settings.dart';
void main() { void main() async {
WidgetsFlutterBinding.ensureInitialized();
final isDark = await loadDarkMode();
runApp( runApp(
MultiProvider( MultiProvider(
providers: [ providers: [
@@ -24,7 +26,7 @@ void main() {
create: (_) => QuoteProvider() create: (_) => QuoteProvider()
), ),
ChangeNotifierProvider( ChangeNotifierProvider(
create: (_) => ThemeProvider() create: (_) => ThemeProvider(isDark: isDark)
) )
], ],
child: HlaskovnikFrontendApp() child: HlaskovnikFrontendApp()
+51 -32
View File
@@ -1,9 +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:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:frontend/values/strings.dart'; import 'package:frontend/values/strings.dart';
import 'package:frontend/values/constants.dart'; import 'package:frontend/values/constants.dart';
@@ -12,10 +9,10 @@ class FlashcardsPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
//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 final screenWidth = MediaQuery.of(context).size.width;
return Card( return Card(
margin: const EdgeInsets.all(Constants.edgeInset), margin: const EdgeInsets.all(Constants.edgeInset),
child: Padding( child: Padding(
@@ -23,43 +20,65 @@ 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: [
const Spacer(),
Padding( Padding(
padding: const EdgeInsets.all(Constants.edgeInset*2), padding: const EdgeInsets.all(Constants.edgeInset),
child: Text(provider.randomQuoteText, child: Text(
provider.randomQuoteText,
textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 35, fontSize: screenWidth * 0.06, // responsive text
fontWeight: FontWeight.w500,
), ),
), ),
), ),
Column(
mainAxisAlignment: MainAxisAlignment.end, const Spacer(),
// Buttons side by side
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
Padding( Expanded(
padding: const EdgeInsets.all(Constants.edgeInset), child: Padding(
child: TextButton.icon( padding: const EdgeInsets.all(Constants.edgeInset),
onPressed: provider.flipRandomQuote, child: TextButton.icon(
icon: Icon(Icons.repeat, onPressed: provider.flipRandomQuote,
size: 35, icon: const Icon(
), Icons.repeat,
label: Text(Strings.flipFlashcard, size: 28,
style: TextStyle( ),
fontSize: 35, label: Text(
Strings.flipFlashcard,
style: TextStyle(
fontSize: screenWidth * 0.045,
),
),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
), ),
), ),
), ),
), ),
Padding( Expanded(
padding: const EdgeInsets.all(Constants.edgeInset), child: Padding(
child: TextButton.icon( padding: const EdgeInsets.all(Constants.edgeInset),
onPressed: provider.nextRandomQuote, child: TextButton.icon(
icon: Icon(Icons.arrow_right_alt, onPressed: provider.nextRandomQuote,
size: 35, icon: const Icon(
), Icons.arrow_right_alt,
label: Text(Strings.nextFlashcard, size: 28,
style: TextStyle( ),
fontSize: 35, label: Text(
Strings.nextFlashcard,
style: TextStyle(
fontSize: screenWidth * 0.045,
),
),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
), ),
), ),
), ),
+20 -2
View File
@@ -3,24 +3,42 @@ import 'package:frontend/provider/quote_provider.dart';
import 'package:frontend/values/constants.dart'; import 'package:frontend/values/constants.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class LeaderboardPage extends StatelessWidget { class LeaderboardPage extends StatefulWidget {
const LeaderboardPage({super.key}); const LeaderboardPage({super.key});
@override
State<LeaderboardPage> createState() => _LeaderboardPageState();
}
class _LeaderboardPageState extends State<LeaderboardPage> {
@override
void initState() {
super.initState();
// reset filter once, only when page is created
WidgetsBinding.instance.addPostFrameCallback((_) {
final provider = context.read<QuoteProvider>();
provider.resetLeaderboardFilter();
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final provider = context.watch<QuoteProvider>(); final provider = context.watch<QuoteProvider>();
return Center( return Center(
child: Column( child: Column(
children: [ children: [
Expanded( Expanded(
child: ListView.builder( child: ListView.builder(
padding: EdgeInsets.all(Constants.edgeInset), padding: EdgeInsets.all(Constants.edgeInset),
itemCount: provider.filteredQuotes.length, itemCount: provider.sortedAuthorCounts.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return return
Card( Card(
color: provider.getLeaderboardEntryBgColor(context, index), color: provider.getLeaderboardEntryBgColor(context, index),
child: ListTile( child: ListTile(
onTap: () { provider.showListPageFilteredByAuthor(context, index); },
contentPadding: EdgeInsets.symmetric( contentPadding: EdgeInsets.symmetric(
vertical: 0, vertical: 0,
horizontal: Constants.edgeInset, horizontal: Constants.edgeInset,
+31 -24
View File
@@ -8,9 +8,14 @@ import 'package:provider/provider.dart';
import 'package:frontend/values/constants.dart'; import 'package:frontend/values/constants.dart';
class ListPage extends StatelessWidget { class ListPage extends StatefulWidget {
const ListPage({super.key}); const ListPage({super.key});
@override
State<ListPage> createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final provider = context.watch<QuoteProvider>(); final provider = context.watch<QuoteProvider>();
@@ -40,9 +45,7 @@ class ListPage extends StatelessWidget {
Center( Center(
child: Padding( child: Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
left: Constants.edgeInset, top: Constants.edgeInset*2
right: Constants.edgeInset,
top: Constants.edgeInset
), ),
child: Text( child: Text(
'${Strings.allQuotesCount} $quoteCount', '${Strings.allQuotesCount} $quoteCount',
@@ -56,7 +59,7 @@ class ListPage extends StatelessWidget {
filterBar(context, provider, themeProvider), filterBar(context, provider, themeProvider),
Expanded( Expanded(
child: ListView.builder( child: ListView.builder(
padding: EdgeInsets.all(Constants.edgeInset), padding: EdgeInsets.all(Constants.edgeInset/2),
itemCount: provider.filteredQuotes.length, itemCount: provider.filteredQuotes.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final quote = provider.filteredQuotes[index]; final quote = provider.filteredQuotes[index];
@@ -114,7 +117,7 @@ class ListPage extends StatelessWidget {
], ],
); );
} }
void _showUpdateDialog(BuildContext context, QuoteProvider provider, Quote quote) { void _showUpdateDialog(BuildContext context, QuoteProvider provider, Quote quote) {
final bodyController = TextEditingController(text: quote.body); final bodyController = TextEditingController(text: quote.body);
final authorController = TextEditingController(text: quote.author); final authorController = TextEditingController(text: quote.author);
@@ -275,12 +278,15 @@ class ListPage extends StatelessWidget {
ListTile( ListTile(
title: Text(Strings.exportSelectedQuotes), title: Text(Strings.exportSelectedQuotes),
trailing: Icon(Icons.import_export), trailing: Icon(Icons.import_export),
onTap: () { onTap: () async {
provider.exportQuotesToFile();
Navigator.pop(context); Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar( final scaffoldMessenger = ScaffoldMessenger.of(context);
final success = await provider.exportWithSAF();
// Make sure the context is still valid
if (!mounted) return;
scaffoldMessenger.showSnackBar(
SnackBar( SnackBar(
content: Text(Strings.fileExportedSuccessfully), content: Text(success ? Strings.fileExportedSuccessfully + provider.exportDir : "export [x]"),
duration: const Duration(seconds: 3), duration: const Duration(seconds: 3),
), ),
); );
@@ -288,21 +294,22 @@ class ListPage extends StatelessWidget {
), ),
ListTile( ListTile(
title: Text(Strings.userPrefersDarkMode), title: Text(Strings.userPrefersDarkMode),
onTap: () { themeProvider.setDarkMode(!themeProvider.isDark); }, trailing: Consumer<ThemeProvider>(
trailing: Row( builder: (context, themeProvider, _) => Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.only(right: Constants.edgeInset), padding: const EdgeInsets.only(right: Constants.edgeInset),
child: Switch( child: Switch(
onChanged: (value) { onChanged: (value) {
themeProvider.setDarkMode(value); themeProvider.setDarkMode(value);
}, },
value: themeProvider.isDark value: themeProvider.isDark,
),
), ),
), Icon(Icons.dark_mode),
Icon(Icons.dark_mode), ],
], ),
), ),
), ),
], ],
+51 -29
View File
@@ -1,6 +1,14 @@
// 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:file_saver/file_saver.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:flutter/foundation.dart'; // for kIsWeb
import 'dart:convert';
import 'dart:typed_data';
import 'package:go_router/go_router.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:frontend/model/quote.dart'; import 'package:frontend/model/quote.dart';
@@ -64,12 +72,10 @@ class QuoteProvider extends ChangeNotifier {
filterController.text = ''; filterController.text = '';
} }
Quote randomQuote = Quote( late Quote randomQuote;
id: 0,
body: "quote text",
author: "dummy"
);
bool randomQuoteDeterminator = true; bool randomQuoteDeterminator = true;
String exportDir = "";
String get randomQuoteText { String get randomQuoteText {
if (randomQuoteDeterminator) { if (randomQuoteDeterminator) {
return randomQuote.body; return randomQuote.body;
@@ -84,6 +90,7 @@ class QuoteProvider extends ChangeNotifier {
} }
void nextRandomQuote() { void nextRandomQuote() {
if (filteredQuotes.isEmpty) return;
final random = Random(); final random = Random();
randomQuote = filteredQuotes[random.nextInt(filteredQuotes.length)]; randomQuote = filteredQuotes[random.nextInt(filteredQuotes.length)];
notifyListeners(); notifyListeners();
@@ -111,17 +118,20 @@ class QuoteProvider extends ChangeNotifier {
Future<void> readQuotes() async { Future<void> readQuotes() async {
_loading = true; _loading = true;
_error = null;
notifyListeners(); notifyListeners();
try { try {
_quotes = await repo.readQuotes(); _quotes = await repo.readQuotes();
populateAuthors();
if (_quotes.isNotEmpty) {
nextRandomQuote();
}
} catch (e) { } catch (e) {
_error = e.toString(); _error = e.toString();
} }
populateAuthors();
_loading = false; _loading = false;
notifyListeners(); notifyListeners();
} }
@@ -169,30 +179,28 @@ class QuoteProvider extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
Future<bool> exportWithSAF() async {
try {
final datetime = DateTime.now().toUtc().toString()
.replaceAll(" ", "T")
.replaceAll(":", "-")
.replaceAll(RegExp(r"\..*"), "");
final filename = 'hlasky_$datetime.txt';
final content = filteredQuotes.map((q) => "${q.body} ${q.author}").join('\n') + '\n';
final bytes = Uint8List.fromList(utf8.encode(content));
Future<Directory> _getSaveDirectory() async { // Show folder picker
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { final folderPath = await FilePicker.platform.getDirectoryPath();
final path = Platform.isWindows if (folderPath == null) return false; // user cancelled
? Platform.environment['USERPROFILE'] exportDir = folderPath;
: Platform.environment['HOME'];
return Directory(path!); final file = File('$folderPath/$filename');
await file.writeAsBytes(bytes);
return true; // export successful
} catch (e) {
return false; // something went wrong
} }
// 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) { Color getLeaderboardEntryBgColor(BuildContext context, int index) {
@@ -218,4 +226,18 @@ class QuoteProvider extends ChangeNotifier {
return Theme.of(context).textTheme.bodyMedium!.color!; return Theme.of(context).textTheme.bodyMedium!.color!;
} }
} }
void showListPageFilteredByAuthor(BuildContext context, int index) {
filterController.text = sortedAuthorCounts[index].key;
filterBy = 0; // filter by author
// navigate to the main page
GoRouter.of(context).go('/');
Future.microtask(() => notifyListeners());
}
void resetLeaderboardFilter() {
filterController.text = '';
filterBy = 0;
notifyListeners();
}
} }
+5 -11
View File
@@ -13,22 +13,16 @@ Future<bool> loadDarkMode() async {
} }
class ThemeProvider extends ChangeNotifier { class ThemeProvider extends ChangeNotifier {
bool _isDark = false; bool _isDark;
bool get isDark => _isDark; bool get isDark => _isDark;
ThemeProvider() { ThemeProvider({required bool isDark}) : _isDark = isDark;
_load(); // load saved value on startup
}
void _load() async { void setDarkMode(bool value) {
_isDark = await loadDarkMode(); if (_isDark == value) return; // avoid unnecessary rebuilds
notifyListeners();
}
void setDarkMode(bool value) async {
_isDark = value; _isDark = value;
notifyListeners(); notifyListeners();
await saveDarkMode(value); saveDarkMode(value);
} }
} }
+2 -2
View File
@@ -21,9 +21,9 @@ class Strings {
static const String flipFlashcard = "Otočit flashkartu"; static const String flipFlashcard = "Otočit flashkartu";
static const String nextFlashcard = "Další flashkarta"; static const String nextFlashcard = "Další flashkarta";
//
//static const String importQuotesFromFile = "Importovat hlášky ze souboru"; //static const String importQuotesFromFile = "Importovat hlášky ze souboru";
static const String fileExportedSuccessfully = "Soubor byl úspěšně vyexportován."; static const String fileExportedSuccessfully = "Soubor byl úspěšně vyexportován do: ";
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";
@@ -6,6 +6,10 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <file_saver/file_saver_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_saver_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSaverPlugin");
file_saver_plugin_register_with_registrar(file_saver_registrar);
} }
@@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
file_saver
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST
@@ -5,8 +5,12 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import file_picker
import file_saver
import shared_preferences_foundation import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
} }
+96
View File
@@ -1,6 +1,14 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.7.0"
async: async:
dependency: transitive dependency: transitive
description: description:
@@ -49,6 +57,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.19.1" version: "1.19.1"
cross_file:
dependency: transitive
description:
name: cross_file
sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937"
url: "https://pub.dev"
source: hosted
version: "0.3.5+2"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
@@ -65,6 +81,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.9" version: "1.0.9"
dbus:
dependency: transitive
description:
name: dbus
sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270
url: "https://pub.dev"
source: hosted
version: "0.7.12"
dio:
dependency: transitive
description:
name: dio
sha256: aff32c08f92787a557dd5c0145ac91536481831a01b4648136373cddb0e64f8c
url: "https://pub.dev"
source: hosted
version: "5.9.2"
dio_web_adapter:
dependency: transitive
description:
name: dio_web_adapter
sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@@ -89,6 +129,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.1" version: "7.0.1"
file_picker:
dependency: "direct main"
description:
name: file_picker
sha256: "57d9a1dd5063f85fa3107fb42d1faffda52fdc948cefd5fe5ea85267a5fc7343"
url: "https://pub.dev"
source: hosted
version: "10.3.10"
file_saver:
dependency: "direct main"
description:
name: file_saver
sha256: "9d93db09bd4da9e43238f9dd485360fc51a5c138eea5ef5f407ec56e58079ac0"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@@ -102,6 +158,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.0" version: "6.0.0"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0"
url: "https://pub.dev"
source: hosted
version: "2.0.34"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@@ -216,6 +280,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.0" version: "1.17.0"
mime:
dependency: transitive
description:
name: mime
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
native_toolchain_c: native_toolchain_c:
dependency: transitive dependency: transitive
description: description:
@@ -296,6 +368,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.3.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
url: "https://pub.dev"
source: hosted
version: "7.0.2"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@@ -469,6 +549,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.1.1"
win32:
dependency: transitive
description:
name: win32
sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e
url: "https://pub.dev"
source: hosted
version: "5.15.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@@ -477,6 +565,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
url: "https://pub.dev"
source: hosted
version: "6.6.1"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:
+2
View File
@@ -39,6 +39,8 @@ dependencies:
http: ^1.2.0 http: ^1.2.0
path_provider: path_provider:
shared_preferences: ^2.1.1 shared_preferences: ^2.1.1
file_picker: ^10.3.10
file_saver: ^0.3.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -6,6 +6,9 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <file_saver/file_saver_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
FileSaverPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSaverPlugin"));
} }
@@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
file_saver
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST