From 24833573909f0b32d332b486797074c096173354 Mon Sep 17 00:00:00 2001 From: NAVAL Date: Wed, 19 Jun 2024 23:29:37 +0200 Subject: [PATCH 1/4] add & fix multiple issues Long term commit includes: - Add new Material 3 themes - Add plant picture view - Add plant name renaming - Fix plant tablet grid - Fix plant date - Add new languages --- .gitignore | 1 + .idea/libraries/Dart_SDK.xml | 19 -- .idea/libraries/KotlinJavaRuntime.xml | 15 - .idea/modules.xml | 9 - .idea/runConfigurations/main_dart.xml | 6 - .idea/workspace.xml | 36 -- android/app/build.gradle | 21 +- android/build.gradle | 22 +- android/settings.gradle | 30 +- assets/undraw_different_love_a-3-rg.svg | 113 ++----- assets/undraw_flowers_vx06.svg | 216 +++++++++++- lib/data/garden.dart | 6 +- lib/l10n/app_ar.arb | 3 +- lib/l10n/app_es.arb | 12 +- lib/l10n/{app_zh-CN.arb => app_zh.arb} | 0 lib/main.dart | 21 +- lib/notifications.dart | 23 +- lib/screens/care_plant.dart | 60 ++-- lib/screens/error.dart | 41 ++- lib/screens/home_page.dart | 85 ++--- lib/screens/manage_plant.dart | 48 +-- lib/screens/picture_viewer.dart | 26 ++ lib/screens/settings.dart | 8 +- lib/themes/darkTheme.dart | 66 ++++ lib/themes/lightTheme.dart | 60 ++++ pubspec.lock | 428 ++++++++++++++---------- pubspec.yaml | 34 +- 27 files changed, 839 insertions(+), 570 deletions(-) delete mode 100644 .idea/libraries/Dart_SDK.xml delete mode 100644 .idea/libraries/KotlinJavaRuntime.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/runConfigurations/main_dart.xml delete mode 100644 .idea/workspace.xml rename lib/l10n/{app_zh-CN.arb => app_zh.arb} (100%) create mode 100644 lib/screens/picture_viewer.dart create mode 100644 lib/themes/darkTheme.dart create mode 100644 lib/themes/lightTheme.dart diff --git a/.gitignore b/.gitignore index a87b4d2..f511463 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ doc/api/ .flutter-plugins .flutter-plugins-dependencies +.idea/ diff --git a/.idea/libraries/Dart_SDK.xml b/.idea/libraries/Dart_SDK.xml deleted file mode 100644 index 896efb4..0000000 --- a/.idea/libraries/Dart_SDK.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/KotlinJavaRuntime.xml b/.idea/libraries/KotlinJavaRuntime.xml deleted file mode 100644 index 2b96ac4..0000000 --- a/.idea/libraries/KotlinJavaRuntime.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index c9befa3..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/.idea/runConfigurations/main_dart.xml b/.idea/runConfigurations/main_dart.xml deleted file mode 100644 index aab7b5c..0000000 --- a/.idea/runConfigurations/main_dart.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index 5b3388c..0000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/app/build.gradle b/android/app/build.gradle index 6b24362..e538da4 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,3 +1,9 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { @@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) { } } -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' @@ -21,9 +22,7 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + def keystoreProperties = new Properties() def keystorePropertiesFile = rootProject.file('key.properties') @@ -49,7 +48,7 @@ android { defaultConfig { applicationId "cat.naval.florae" - minSdkVersion 16 + minSdkVersion flutter.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName @@ -74,7 +73,3 @@ android { flutter { source '../..' } - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} diff --git a/android/build.gradle b/android/build.gradle index c04a866..a168e39 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,19 +1,7 @@ -buildscript { - ext.kotlin_version = '1.7.10' - ext { - compileSdkVersion = 33 // or latest - targetSdkVersion = 33 // or latest - appCompatVersion = "1.4.2" // or latest - } - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } +ext { + compileSdkVersion = 34 + targetSdkVersion = 34 + appCompatVersion = "1.4.2" } allprojects { @@ -37,6 +25,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/android/settings.gradle b/android/settings.gradle index 44e62bc..599ca9d 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,11 +1,25 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.4.2" apply false + id "org.jetbrains.kotlin.android" version "1.7.10" apply false +} + +include ":app" \ No newline at end of file diff --git a/assets/undraw_different_love_a-3-rg.svg b/assets/undraw_different_love_a-3-rg.svg index ac35998..925ec3f 100644 --- a/assets/undraw_different_love_a-3-rg.svg +++ b/assets/undraw_different_love_a-3-rg.svg @@ -7,53 +7,16 @@ version="1.1" id="svg111" sodipodi:docname="undraw_different_love_a-3-rg.svg" - inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)" + inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> - - different love - - - + id="path33" + transform="translate(-226.66547 -50.81143)" /> - + id="path79" + transform="translate(-226.66547 -50.81143)" /> + id="path81" + transform="translate(-226.66547 -50.81143)" /> + id="path83" + transform="translate(-226.66547 -50.81143)" /> + id="path85" + transform="translate(-226.66547 -50.81143)" /> + id="path87" + transform="translate(-226.66547 -50.81143)" /> + id="path91" + transform="translate(-226.66547 -50.81143)" /> + fill="#64FFDA" + id="path93" + transform="translate(-226.66547 -50.81143)" /> + id="path95" + transform="translate(-226.66547 -50.81143)" /> + id="path97" + transform="translate(-226.66547 -50.81143)" /> + id="path99" + transform="translate(-226.66547 -50.81143)" /> + id="path101" + transform="translate(-226.66547 -50.81143)" /> + id="path103" + transform="translate(-226.66547 -50.81143)" /> + id="path105" + transform="translate(-226.66547 -50.81143)" /> + fill="#64FFDA" + id="path107" + transform="translate(-226.66547 -50.81143)" /> flowers \ No newline at end of file + + + + + + + + + + + + flowers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/data/garden.dart b/lib/data/garden.dart index 384ba6c..b4ac789 100644 --- a/lib/data/garden.dart +++ b/lib/data/garden.dart @@ -31,14 +31,12 @@ class Garden { List allPlants = await getAllPlants(); bool status; - var plantIndex = - allPlants.indexWhere((element) => element.name == plant.name); + var plantIndex = allPlants.indexWhere((element) => element.id == plant.id); if (plantIndex == -1) { allPlants.add(plant); status = false; } else { - allPlants[allPlants.indexWhere((element) => element.name == plant.name)] = - plant; + allPlants[plantIndex] = plant; status = true; } String jsonPlants = jsonEncode(allPlants); diff --git a/lib/l10n/app_ar.arb b/lib/l10n/app_ar.arb index 935ece1..8cf081f 100755 --- a/lib/l10n/app_ar.arb +++ b/lib/l10n/app_ar.arb @@ -51,8 +51,7 @@ "testNotificationTitle": "إشعار تجريبي لتطبيق فلوري", "testNotificationBody": "هذا نص تجريبي", "aboutFloraeButton": "عن فلوراي", - "notificationInfo": "سيتم إعادة ضبط وقت الإشعارات عند دخولك للتطبيق مجدداً.\n\n - مع العلم بأن بعض الأجهزة تطبق سياسة توفير طاقة شديدة قد تؤدي إلى عدم إصدار الإشعارات بشكل صحيح.", + "notificationInfo": "سيتم إعادة ضبط وقت الإشعارات عند دخولك للتطبيق مجدداً.\n\n مع العلم بأن بعض الأجهزة تطبق سياسة توفير طاقة شديدة قد تؤدي إلى عدم إصدار الإشعارات بشكل صحيح.", "careNotificationTitle": "نباتات بحاجة إلى عناية", "careNotificationName": "تذكير العناية", "careNotificationDescription": "استلم إشعارات العناية عند حاجة نباتاتك إلى إهتمام", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index a66fb06..7089358 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -1,14 +1,14 @@ { "no": "No", "yes": "Sí", - "mainNoCares": "¡Genial! No hay plantas que requieran atención", - "mainNoPlants": "El jardín está vacío, ¿Plantamos algo?", + "mainNoCares": "¡Genial! No hay plantas que necesiten atención", + "mainNoPlants": "El jardín está vacío, ¿plantamos algo?", "buttonGarden": "Jardín", "buttonToday": "Hoy", "tooltipCareAll": "Cuidar todas las plantas", "tooltipShowCalendar": "Mostrar calendario", "tooltipSettings": "Ajustes", - "tooltipNewPlant": "Añadir nueva planta", + "tooltipNewPlant": "Añadir una nueva planta", "selectDays": "Número de días", "ok": "Aceptar", "every": "cada", @@ -51,10 +51,10 @@ "testNotificationTitle": "Notificación de prueba de Florae", "testNotificationBody": "Esto es un mensaje de prueba", "aboutFloraeButton": "Sobre Florae", - "notificationInfo": "El tiempo entre notificaciones se restablecerá cuando accedas a la aplicación.\n\nTen en cuenta que algunos fabricantes aplican agresivas optimizaciones de batería que pueden provocar que las notificaciones no se emitan adecuadamente.", + "notificationInfo": "El periodo entre notificaciones se restablecerá cuando accedas a la aplicación.\n\nTen en cuenta que algunos fabricantes aplican optimizaciones de batería muy agresivas, que pueden provocar que las notificaciones no funcionen adecuadamente.", "careNotificationTitle": "Hay plantas que requieren atención", "careNotificationName": "Alertas de cuidados", - "careNotificationDescription": "Recibe notificaciones cuando tus plantas requieran atención", + "careNotificationDescription": "Recibe notificaciones cuando tus plantas te necesiten", "deletePlantTitle": "Borrar planta", - "deletePlantBody": "Vas a proceder a eliminar esta planta definitivamente, esta acción no se puede deshacer." + "deletePlantBody": "Esta acción eliminará la planta de forma definitiva. ¿Deseas hacerlo?" } \ No newline at end of file diff --git a/lib/l10n/app_zh-CN.arb b/lib/l10n/app_zh.arb similarity index 100% rename from lib/l10n/app_zh-CN.arb rename to lib/l10n/app_zh.arb diff --git a/lib/main.dart b/lib/main.dart index d8c1296..2ba2590 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,8 @@ import 'dart:io'; import 'package:background_fetch/background_fetch.dart'; import 'package:florae/screens/error.dart'; +import 'package:florae/themes/darkTheme.dart'; +import 'package:florae/themes/lightTheme.dart'; import 'package:flutter/material.dart'; import 'data/care.dart'; import 'data/plant.dart'; @@ -114,21 +116,12 @@ class FloraeApp extends StatelessWidget { Locale('es'), // Spanish Locale('fr'), // French Locale('nl'), // Dutch + Locale('zh'), // Chinese (Simplified, People's Republic of China) + Locale('ru'), // Russian + Locale('ar'), // Arabic ], - theme: ThemeData( - primaryColor: Colors.teal, - // This is the theme of your application. - // - // Try running your application with "flutter run". You'll see the - // application has a blue toolbar. Then, without quitting the app, try - // changing the primarySwatch below to Colors.green and then invoke - // "hot reload" (press "r" in the console where you ran "flutter run", - // or simply save your changes to "hot reload" in a Flutter IDE). - // Notice that the counter didn't reset back to zero; the application - // is not restarted. - primarySwatch: Colors.teal, - fontFamily: "NotoSans", - scaffoldBackgroundColor: Colors.grey[100]), + theme: buildLightThemeData(), + darkTheme: buildDarkThemeData(), home: const MyHomePage(title: 'Today')); } } diff --git a/lib/notifications.dart b/lib/notifications.dart index 5cf455c..6d908d7 100644 --- a/lib/notifications.dart +++ b/lib/notifications.dart @@ -5,28 +5,11 @@ import 'dart:async'; final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); - void _requestPermissions() { flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< - IOSFlutterLocalNotificationsPlugin>() - ?.requestPermissions( - alert: true, - badge: true, - sound: true, - ); - flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation< - MacOSFlutterLocalNotificationsPlugin>() - ?.requestPermissions( - alert: true, - badge: true, - sound: true, - ); - - flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin>()?.requestPermission(); - + AndroidFlutterLocalNotificationsPlugin>() + ?.requestNotificationsPermission(); } Future _createNotificationChannel( @@ -44,10 +27,8 @@ Future _createNotificationChannel( } void initNotifications(String channelName, String channelDescription) async { - //_startForegroundService(); _requestPermissions(); _createNotificationChannel("care_reminder", channelName, channelDescription); - //_createNotificationChannelGroup(); const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('@drawable/ic_stat_florae'); diff --git a/lib/screens/care_plant.dart b/lib/screens/care_plant.dart index ad4fdf0..5c1954d 100644 --- a/lib/screens/care_plant.dart +++ b/lib/screens/care_plant.dart @@ -1,5 +1,6 @@ import 'dart:io'; import 'package:florae/data/plant.dart'; +import 'package:florae/screens/picture_viewer.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import '../data/care.dart'; @@ -115,7 +116,7 @@ class _CarePlantScreen extends State { IconButton( icon: const Icon(Icons.edit), iconSize: 25, - color: Colors.black54, + color: Theme.of(context).colorScheme.primary, tooltip: AppLocalizations.of(context)!.tooltipEdit, onPressed: () async { await Navigator.push( @@ -127,11 +128,7 @@ class _CarePlantScreen extends State { }, ) ], - titleTextStyle: const TextStyle( - color: Colors.black54, - fontSize: 40, - fontWeight: FontWeight.w800, - fontFamily: "NotoSans"), + titleTextStyle: Theme.of(context).textTheme.displayLarge, ), //passing in the ListView.builder body: SingleChildScrollView( @@ -145,25 +142,38 @@ class _CarePlantScreen extends State { borderRadius: BorderRadius.circular(10.0), ), elevation: 2, - child: SizedBox( - child: Column( - children: [ - ClipRRect( - child: SizedBox( - height: 220, - child: plant.picture!.contains("assets/") - ? Image.asset( - plant.picture!, - fit: BoxFit.fitHeight, - ) - : Image.file( - File(plant.picture!), - fit: BoxFit.fitWidth, - ), + child: InkWell( + onTap: () { + if (!plant.picture!.contains("assets/")) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PictureViewer( + picture: plant.picture, + )), + ); + } + }, + child: SizedBox( + child: Column( + children: [ + ClipRRect( + child: SizedBox( + height: 220, + child: plant.picture!.contains("assets/") + ? Image.asset( + plant.picture!, + fit: BoxFit.fitHeight, + ) + : Image.file( + File(plant.picture!), + fit: BoxFit.fitWidth, + ), + ), ), - ), - ], - )), + ], + )), + ), ), Card( semanticContainer: true, @@ -245,7 +255,7 @@ class _CarePlantScreen extends State { }, label: Text(AppLocalizations.of(context)!.careButton), icon: const Icon(Icons.check), - backgroundColor: Colors.teal, + backgroundColor: Theme.of(context).colorScheme.secondary, ) ], ), diff --git a/lib/screens/error.dart b/lib/screens/error.dart index 8bd90d3..9df0bcd 100644 --- a/lib/screens/error.dart +++ b/lib/screens/error.dart @@ -22,11 +22,7 @@ class ErrorPage extends StatelessWidget { // Here we take the value from the MyHomePage object that was created by // the App.build method, and use it to set our appbar title. title: const FittedBox(fit: BoxFit.fitWidth, child: Text("Error")), - titleTextStyle: const TextStyle( - color: Colors.black54, - fontSize: 40, - fontWeight: FontWeight.w800, - fontFamily: "NotoSans"), + titleTextStyle: Theme.of(context).textTheme.displayLarge, ), body: Padding( padding: const EdgeInsets.symmetric(), @@ -44,7 +40,7 @@ class ErrorPage extends StatelessWidget { style: TextStyle(color: Colors.redAccent, fontSize: 45)), ), const Text( - "Something is not right here...", + "An error has occurred.", style: TextStyle( color: Colors.redAccent, fontSize: 25, @@ -55,30 +51,39 @@ class ErrorPage extends StatelessWidget { left: 20, bottom: 50, right: 20, top: 10), child: SelectableText(errorDetails.exception.toString(), textAlign: TextAlign.center, - style: const TextStyle(color: Colors.black, fontSize: 20)), + style: const TextStyle(fontSize: 20)), ), OutlinedButton.icon( icon: const Icon(Icons.error, size: 18), - style: TextButton.styleFrom(foregroundColor: Colors.redAccent, backgroundColor: Colors.white), + style: OutlinedButton.styleFrom( + side:const BorderSide( + color: Colors.transparent, + ), + foregroundColor: Colors.redAccent, + backgroundColor: Colors.white), onPressed: () => showDialog( context: context, builder: (BuildContext context) => AlertDialog( title: const Text('Error details'), - content: SingleChildScrollView( - scrollDirection: Axis.vertical, //.horizontal - child: SelectableText(errorDetails.toString(), - textAlign: TextAlign.center, - style: const TextStyle( - color: Colors.black, fontSize: 12, fontFamily: "monospace")), - ), + content: SingleChildScrollView( + scrollDirection: Axis.vertical, //.horizontal + child: SelectableText(errorDetails.toString(), + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 12, + fontFamily: "monospace")), + ), actions: [ TextButton( - onPressed: () => Clipboard.setData(ClipboardData(text: errorDetails.toString())), - child: const Text('Copy', style: TextStyle(color: Colors.redAccent)), + onPressed: () => Clipboard.setData( + ClipboardData(text: errorDetails.toString())), + child: const Text('Copy', + style: TextStyle(color: Colors.redAccent)), ), TextButton( onPressed: () => Navigator.pop(context, 'OK'), - child: const Text('OK', style: TextStyle(color: Colors.redAccent)) , + child: const Text('OK', + style: TextStyle(color: Colors.redAccent)), ), ], ), diff --git a/lib/screens/home_page.dart b/lib/screens/home_page.dart index a9060cb..359707f 100644 --- a/lib/screens/home_page.dart +++ b/lib/screens/home_page.dart @@ -20,6 +20,8 @@ import 'manage_plant.dart'; import 'care_plant.dart'; import 'settings.dart'; +enum Page { today, garden } + class MyHomePage extends StatefulWidget { const MyHomePage({Key? key, required this.title}) : super(key: key); @@ -43,7 +45,7 @@ class _MyHomePageState extends State { Map> _cares = {}; bool _dateFilterEnabled = false; DateTime _dateFilter = DateTime.now(); - int _selectedIndex = 0; + Page _currentPage = Page.today; @override void dispose() { @@ -132,14 +134,6 @@ class _MyHomePageState extends State { BackgroundFetch.finish(taskId); } - void _onItemTapped(int index) { - setState(() { - _dateFilterEnabled = false; - _selectedIndex = index; - _loadPlants(); - }); - } - Future _showWaterAllPlantsDialog() async { return showDialog( context: context, @@ -182,9 +176,13 @@ class _MyHomePageState extends State { mainAxisSize: MainAxisSize.min, children: [ SvgPicture.asset( - _selectedIndex == 0 - ? "assets/undraw_fall_thyk.svg" - : "assets/undraw_blooming_re_2kc4.svg", + _currentPage == Page.today + ? (Theme.of(context).brightness == Brightness.dark) + ? "assets/undraw_different_love_a-3-rg.svg" + : "assets/undraw_fall_thyk.svg" + : (Theme.of(context).brightness == Brightness.dark) + ? "assets/undraw_flowers_vx06.svg" + : "assets/undraw_blooming_re_2kc4.svg", semanticsLabel: 'Fall', alignment: Alignment.center, height: 250, @@ -193,14 +191,14 @@ class _MyHomePageState extends State { padding: const EdgeInsets.all(10), //apply padding to all four sides child: Text( - _selectedIndex == 0 + _currentPage == Page.today ? AppLocalizations.of(context)!.mainNoCares : AppLocalizations.of(context)!.mainNoPlants, style: TextStyle( fontFamily: 'NotoSans', fontWeight: FontWeight.w500, fontSize: 0.065 * MediaQuery.of(context).size.width, - color: const Color(0x78000000), + color: Theme.of(context).colorScheme.primary, ), textAlign: TextAlign.center, ), @@ -217,7 +215,7 @@ class _MyHomePageState extends State { .format(_dateFilter) + " " + DateFormat('d').format(_dateFilter); - } else if (_selectedIndex == 1) { + } else if (_currentPage == Page.garden) { return AppLocalizations.of(context)!.buttonGarden; } else { return AppLocalizations.of(context)!.buttonToday; @@ -242,28 +240,24 @@ class _MyHomePageState extends State { // Here we take the value from the MyHomePage object that was created by // the App.build method, and use it to set our appbar title. title: FittedBox(fit: BoxFit.fitWidth, child: Text(title)), - titleTextStyle: const TextStyle( - color: Colors.black54, - fontSize: 40, - fontWeight: FontWeight.w800, - fontFamily: "NotoSans"), + titleTextStyle: Theme.of(context).textTheme.displayLarge, actions: [ - _selectedIndex == 0 + _currentPage == Page.today ? IconButton( icon: const Icon(Icons.checklist_rounded), iconSize: 25, - color: Colors.black54, + color: Theme.of(context).colorScheme.primary, tooltip: AppLocalizations.of(context)!.tooltipCareAll, onPressed: () { _showWaterAllPlantsDialog(); }, ) : const SizedBox.shrink(), - _selectedIndex == 0 + _currentPage == Page.today ? IconButton( icon: const Icon(Icons.calendar_today), iconSize: 25, - color: Colors.black54, + color: Theme.of(context).colorScheme.primary, tooltip: AppLocalizations.of(context)!.tooltipShowCalendar, onPressed: () async { DateTime? result = await showDatePicker( @@ -287,7 +281,7 @@ class _MyHomePageState extends State { IconButton( icon: const Icon(Icons.settings), iconSize: 25, - color: Colors.black54, + color: Theme.of(context).colorScheme.primary, tooltip: AppLocalizations.of(context)!.tooltipSettings, onPressed: () async { await Navigator.push( @@ -315,30 +309,39 @@ class _MyHomePageState extends State { // Vertical space around the grid verticalGridMargin: 10, // The minimum item width (can be smaller, if the layout constraints are smaller) - minItemWidth: 300, + minItemWidth: 150, // The minimum items to show in a single row. Takes precedence over minItemWidth minItemsPerRow: 2, // The maximum items to show in a single row. Can be useful on large screens - maxItemsPerRow: 2, + maxItemsPerRow: 6, children: _buildPlantCards(context) // Changed code ), - bottomNavigationBar: BottomNavigationBar( - items: [ - BottomNavigationBarItem( - icon: const Icon(Icons.eco), + bottomNavigationBar: NavigationBar( + onDestinationSelected: (int index) { + setState(() { + _dateFilterEnabled = false; + _currentPage = Page.values[index]; + _loadPlants(); + }); + }, + selectedIndex: _currentPage.index, + destinations: [ + NavigationDestination( + selectedIcon: + Icon(Icons.eco, color: Theme.of(context).colorScheme.surface), + icon: const Icon(Icons.eco_outlined), label: AppLocalizations.of(context)!.buttonToday, ), - BottomNavigationBarItem( - icon: const Icon(Icons.grass), + NavigationDestination( + selectedIcon: + Icon(Icons.grass, color: Theme.of(context).colorScheme.surface), + icon: const Icon(Icons.grass_outlined), label: AppLocalizations.of(context)!.buttonGarden, ), ], - selectedItemColor: Colors.teal, - currentIndex: _selectedIndex, - onTap: _onItemTapped, ), - floatingActionButtonLocation: FloatingActionButtonLocation.endDocked, + floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, floatingActionButton: FloatingActionButton( onPressed: () async { await Navigator.push( @@ -348,13 +351,13 @@ class _MyHomePageState extends State { title: "Manage plant", update: false), )); setState(() { - _selectedIndex = 1; + _currentPage = Page.garden; _loadPlants(); }); }, tooltip: AppLocalizations.of(context)!.tooltipNewPlant, child: const Icon(Icons.add), - backgroundColor: Colors.teal, + backgroundColor: Theme.of(context).colorScheme.secondary, ), // This trailing comma makes auto-formatting nicer for build methods. ); } @@ -369,7 +372,7 @@ class _MyHomePageState extends State { bool inserted = false; bool requiresInsert = false; - if (_selectedIndex == 0) { + if (_currentPage == Page.today) { for (Plant p in allPlants) { cares[p.name] = []; for (Care c in p.cares) { @@ -398,6 +401,8 @@ class _MyHomePageState extends State { } } else { plants = allPlants; + // Alphabetically sort + plants.sort((a, b) => a.name.compareTo(b.name)); for (Plant p in allPlants) { cares[p.name] = []; for (Care c in p.cares) { diff --git a/lib/screens/manage_plant.dart b/lib/screens/manage_plant.dart index 3867199..89b3f56 100644 --- a/lib/screens/manage_plant.dart +++ b/lib/screens/manage_plant.dart @@ -131,6 +131,7 @@ class _ManagePlantScreen extends State { effected: care.effected, id: care.name.hashCode); } + _planted = widget.plant!.createdAt; nameController.text = widget.plant!.name; descriptionController.text = widget.plant!.description; locationController.text = widget.plant!.location ?? ""; @@ -201,11 +202,7 @@ class _ManagePlantScreen extends State { elevation: 0.0, backgroundColor: Colors.transparent, shadowColor: Colors.transparent, - titleTextStyle: const TextStyle( - color: Colors.black54, - fontSize: 40, - fontWeight: FontWeight.w800, - fontFamily: "NotoSans"), + titleTextStyle: Theme.of(context).textTheme.displayLarge, ), //passing in the ListView.builder body: SingleChildScrollView( @@ -276,7 +273,6 @@ class _ManagePlantScreen extends State { child: Column(children: [ TextFormField( controller: nameController, - enabled: !widget.update, validator: (value) { if (widget.update) { return null; @@ -289,21 +285,12 @@ class _ManagePlantScreen extends State { } return null; }, - cursorColor: Colors.teal, + cursorColor: Theme.of(context).colorScheme.secondary, maxLength: 20, decoration: InputDecoration( icon: const Icon(Icons.local_florist), labelText: AppLocalizations.of(context)!.labelName, - labelStyle: const TextStyle( - decorationColor: Colors.teal, - ), - fillColor: Colors.teal, - focusColor: Colors.teal, - hoverColor: Colors.teal, helperText: AppLocalizations.of(context)!.exampleName, - enabledBorder: const UnderlineInputBorder(), - focusedBorder: const UnderlineInputBorder( - borderSide: BorderSide(color: Colors.teal)), ), ), TextFormField( @@ -313,42 +300,24 @@ class _ManagePlantScreen extends State { maxLines: 3, // when user presses enter it will adapt to it controller: descriptionController, - cursorColor: Colors.teal, + cursorColor: Theme.of(context).colorScheme.secondary, maxLength: 100, decoration: InputDecoration( icon: const Icon(Icons.topic), labelText: AppLocalizations.of(context)!.labelDescription, - labelStyle: const TextStyle( - decorationColor: Colors.teal, - ), - fillColor: Colors.teal, - focusColor: Colors.teal, - hoverColor: Colors.teal, - enabledBorder: const UnderlineInputBorder(), - focusedBorder: const UnderlineInputBorder( - borderSide: BorderSide(color: Colors.teal)), ), ), TextFormField( controller: locationController, - cursorColor: Colors.teal, + cursorColor: Theme.of(context).colorScheme.secondary, maxLength: 20, decoration: InputDecoration( icon: const Icon(Icons.location_on), labelText: AppLocalizations.of(context)!.labelLocation, - labelStyle: const TextStyle( - decorationColor: Colors.teal, - ), - fillColor: Colors.teal, - focusColor: Colors.teal, - hoverColor: Colors.teal, helperText: AppLocalizations.of(context)!.exampleLocation, - enabledBorder: const UnderlineInputBorder(), - focusedBorder: const UnderlineInputBorder( - borderSide: BorderSide(color: Colors.teal)), ), ), ]), @@ -383,8 +352,7 @@ class _ManagePlantScreen extends State { DateTime? result = await showDatePicker( context: context, initialDate: DateTime.now(), - firstDate: - DateTime.now().subtract(const Duration(days: 1000)), + firstDate: DateTime(1901, 1, 1), lastDate: DateTime.now()); setState(() { _planted = result ?? DateTime.now(); @@ -416,7 +384,7 @@ class _ManagePlantScreen extends State { final newPlant = Plant( id: widget.plant != null ? widget.plant!.id - : nameController.text.hashCode, + : DateTime.now().microsecondsSinceEpoch, name: nameController.text, createdAt: _planted, description: descriptionController.text, @@ -446,7 +414,7 @@ class _ManagePlantScreen extends State { }, label: Text(AppLocalizations.of(context)!.saveButton), icon: const Icon(Icons.save), - backgroundColor: Colors.teal, + backgroundColor: Theme.of(context).colorScheme.secondary, ), ); } diff --git a/lib/screens/picture_viewer.dart b/lib/screens/picture_viewer.dart new file mode 100644 index 0000000..c6285b9 --- /dev/null +++ b/lib/screens/picture_viewer.dart @@ -0,0 +1,26 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class PictureViewer extends StatelessWidget { + const PictureViewer({super.key, this.picture}); + + final String? picture; + + @override + Widget build(BuildContext context) { + return Center( + child: InteractiveViewer( + panEnabled: false, + boundaryMargin: const EdgeInsets.all(100), + minScale: 0.5, + maxScale: 2, + child: Image.file( + File(picture!), + fit: BoxFit.cover, + ), + ), + ); + } +} diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart index 68c47bf..a3f37a5 100644 --- a/lib/screens/settings.dart +++ b/lib/screens/settings.dart @@ -88,11 +88,7 @@ class _SettingsScreen extends State { elevation: 0.0, backgroundColor: Colors.transparent, shadowColor: Colors.transparent, - titleTextStyle: const TextStyle( - color: Colors.black54, - fontSize: 40, - fontWeight: FontWeight.w800, - fontFamily: "NotoSans"), + titleTextStyle: Theme.of(context).textTheme.displayLarge, ), //passing in the ListView.builder body: SingleChildScrollView( @@ -177,7 +173,7 @@ class _SettingsScreen extends State { }, label: Text(AppLocalizations.of(context)!.saveButton), icon: const Icon(Icons.save), - backgroundColor: Colors.teal, + backgroundColor: Theme.of(context).colorScheme.secondary, ), ); } diff --git a/lib/themes/darkTheme.dart b/lib/themes/darkTheme.dart new file mode 100644 index 0000000..d1dffc5 --- /dev/null +++ b/lib/themes/darkTheme.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; + +ThemeData buildDarkThemeData() { + return ThemeData( + brightness: Brightness.dark, + scaffoldBackgroundColor: Colors.black, + primaryColor: Colors.tealAccent, + fontFamily: "NotoSans", + colorScheme: const ColorScheme.dark( + surfaceTint: Colors.transparent, + primary: Colors.white70, + //onPrimary: Colors.white, + secondary: Colors.tealAccent), + textTheme: const TextTheme( + displayLarge: TextStyle( + color: Colors.white70, + fontSize: 40, + fontWeight: FontWeight.w800, + fontFamily: "NotoSans"), + titleLarge: TextStyle( + color: Colors.white70, + ), + titleMedium: TextStyle( + color: Colors.white70, + )), + listTileTheme: const ListTileThemeData( + iconColor: Colors.white70, + subtitleTextStyle: TextStyle(color: Colors.white70)), + textButtonTheme: TextButtonThemeData( + style: ButtonStyle( + foregroundColor: + WidgetStateProperty.resolveWith((state) => Colors.white70)), + ), + bottomNavigationBarTheme: const BottomNavigationBarThemeData( + backgroundColor: Color(0xFF121212), + selectedItemColor: Colors.tealAccent, + unselectedItemColor: Colors.white30, + elevation: 10, + showUnselectedLabels: true, + ), + checkboxTheme: CheckboxThemeData( + fillColor: WidgetStateColor.resolveWith((states) => + states.contains(WidgetState.selected) + ? Colors.tealAccent + : Colors.transparent), + checkColor: WidgetStateProperty.all(Colors.black), + side: const BorderSide(width: 2.0, color: Colors.white30)), + inputDecorationTheme: InputDecorationTheme( + labelStyle: const TextStyle(color: Colors.white60), + iconColor: WidgetStateColor.resolveWith((states) => + states.contains(WidgetState.focused) + ? Colors.tealAccent + : Colors.white60), + fillColor: Colors.tealAccent, + focusColor: Colors.tealAccent, + hoverColor: Colors.tealAccent, + helperStyle: const TextStyle(color: Colors.white60), + hintStyle: const TextStyle( + decorationColor: Colors.tealAccent, color: Colors.tealAccent), + enabledBorder: const UnderlineInputBorder( + borderSide: BorderSide(color: Colors.white70)), + focusedBorder: const UnderlineInputBorder( + borderSide: BorderSide(color: Colors.teal)), + ), + ); +} diff --git a/lib/themes/lightTheme.dart b/lib/themes/lightTheme.dart new file mode 100644 index 0000000..7cd3e1f --- /dev/null +++ b/lib/themes/lightTheme.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; + +ThemeData buildLightThemeData() { + return ThemeData( + primaryColor: Colors.teal, + scaffoldBackgroundColor: Colors.grey[100], + fontFamily: "NotoSans", + colorScheme: const ColorScheme.light( + surfaceTint: Colors.transparent, + primary: Colors.black54, + secondary: Colors.teal), + textTheme: const TextTheme( + displayLarge: TextStyle( + color: Colors.black54, + fontSize: 40, + fontWeight: FontWeight.w800, + fontFamily: "NotoSans"), + titleLarge: TextStyle( + color: Colors.black, + ), + titleMedium: TextStyle( + color: Colors.black54, + )), + listTileTheme: const ListTileThemeData( + iconColor: Colors.black54, + subtitleTextStyle: TextStyle(color: Colors.black54)), + textButtonTheme: TextButtonThemeData( + style: ButtonStyle( + foregroundColor: + WidgetStateProperty.resolveWith((state) => Colors.black)), + ), + bottomNavigationBarTheme: const BottomNavigationBarThemeData( + backgroundColor: Colors.white, + selectedItemColor: Colors.teal, + elevation: 10, + showUnselectedLabels: true, + ), + checkboxTheme: CheckboxThemeData( + fillColor: WidgetStateProperty.resolveWith((states) => + states.contains(WidgetState.selected) + ? Colors.teal + : Colors.transparent), + checkColor: WidgetStateProperty.all(Colors.white), + side: const BorderSide(width: 2.0, color: Colors.black54)), + inputDecorationTheme: InputDecorationTheme( + labelStyle: const TextStyle(color: Colors.black), + iconColor: WidgetStateColor.resolveWith((states) => + states.contains(WidgetState.focused) ? Colors.teal : Colors.black54), + fillColor: Colors.teal, + focusColor: Colors.teal, + hoverColor: Colors.teal, + hintStyle: + const TextStyle(decorationColor: Colors.teal, color: Colors.teal), + helperStyle: const TextStyle(color: Colors.black54), + enabledBorder: const UnderlineInputBorder(), + focusedBorder: const UnderlineInputBorder( + borderSide: BorderSide(color: Colors.teal)), + ), + ); +} diff --git a/pubspec.lock b/pubspec.lock index 9f4157e..b7f06c8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,42 +5,42 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0c80aeab9bc807ab10022cd3b2f4cf2ecdf231949dc1ddd9442406a003f19201" + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" url: "https://pub.dev" source: hosted - version: "52.0.0" + version: "67.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: cd8ee83568a77f3ae6b913a36093a1c9b1264e7cb7f834d9ddd2311dade9c1f4 + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "6.4.1" args: dependency: transitive description: name: args - sha256: "139d809800a412ebb26a3892da228b2d0ba36f0ef5d9a82166e5e52ec8d61611" + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.2" async: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" background_fetch: dependency: "direct main" description: name: background_fetch - sha256: c49953b72c42e4851cd3837bd2488fab84c0e653260293d9f4730ff00d4b3f99 + sha256: b5c298c911bc2ce41152668bc72eb0488f0665d75bc6d1e69e7d8367763eddcd url: "https://pub.dev" source: hosted - version: "1.1.5" + version: "1.3.5" boolean_selector: dependency: transitive description: @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: build - sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" build_config: dependency: transitive description: @@ -69,34 +69,34 @@ packages: dependency: transitive description: name: build_daemon - sha256: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf" + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "4.0.1" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "7c35a3a7868626257d8aee47b51c26b9dba11eaddf3431117ed2744951416aab" + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.4.2" build_runner: dependency: "direct dev" description: name: build_runner - sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 + sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.11" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292" + sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" url: "https://pub.dev" source: hosted - version: "7.2.7" + version: "7.3.0" built_collection: dependency: transitive description: @@ -109,26 +109,26 @@ packages: dependency: transitive description: name: built_value - sha256: "169565c8ad06adb760c3645bf71f00bff161b00002cace266cad42c5d22a7725" + sha256: fedde275e0a6b798c3296963c5cd224e3e1b55d0e478d5b7e65e6b540f363a0e url: "https://pub.dev" source: hosted - version: "8.4.3" + version: "8.9.1" characters: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" checked_yaml: dependency: transitive description: name: checked_yaml - sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311" + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.0.3" clock: dependency: transitive description: @@ -141,18 +141,18 @@ packages: dependency: transitive description: name: code_builder - sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe" + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 url: "https://pub.dev" source: hosted - version: "4.4.0" + version: "4.10.0" collection: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.18.0" convert: dependency: transitive description: @@ -165,42 +165,34 @@ packages: dependency: transitive description: name: cross_file - sha256: f71079978789bc2fe78d79227f1f8cfe195b31bbd8db2399b0d15a4b96fb843b + sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" url: "https://pub.dev" source: hosted - version: "0.3.3+2" + version: "0.3.4+1" crypto: dependency: transitive description: name: crypto - sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 - url: "https://pub.dev" - source: hosted - version: "3.0.2" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "3.0.3" dart_style: dependency: transitive description: name: dart_style - sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4" + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.3.6" dbus: dependency: transitive description: name: dbus - sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263" + sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" url: "https://pub.dev" source: hosted - version: "0.7.8" + version: "0.7.10" fake_async: dependency: transitive description: @@ -213,10 +205,10 @@ packages: dependency: transitive description: name: ffi - sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.2" file: dependency: transitive description: @@ -225,6 +217,38 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.4" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + url: "https://pub.dev" + source: hosted + version: "0.9.2+1" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: b15c3da8bd4908b9918111fa486903f5808e388b8d1c559949f584725a6594d6 + url: "https://pub.dev" + source: hosted + version: "0.9.3+3" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0 + url: "https://pub.dev" + source: hosted + version: "0.9.3+1" fixnum: dependency: transitive description: @@ -250,26 +274,26 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: "57d0012730780fe137260dd180e072c18a73fbeeb924cdc029c18aaa0f338d64" + sha256: "40e6fbd2da7dcc7ed78432c5cdab1559674b4af035fddbfb2f9a8f9c2112fcef" url: "https://pub.dev" source: hosted - version: "9.9.1" + version: "17.1.2" flutter_local_notifications_linux: dependency: transitive description: name: flutter_local_notifications_linux - sha256: b472bfc173791b59ede323661eae20f7fff0b6908fea33dd720a6ef5d576bae8 + sha256: "33f741ef47b5f63cc7f78fe75eeeac7e19f171ff3c3df054d84c1e38bedb6a03" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "4.0.0+1" flutter_local_notifications_platform_interface: dependency: transitive description: name: flutter_local_notifications_platform_interface - sha256: "21bceee103a66a53b30ea9daf677f990e5b9e89b62f222e60dd241cd08d63d3a" + sha256: "340abf67df238f7f0ef58f4a26d2a83e1ab74c77ab03cd2b2d5018ac64db30b7" url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "7.1.0" flutter_localizations: dependency: "direct main" description: flutter @@ -279,18 +303,18 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "60fc7b78455b94e6de2333d2f95196d32cf5c22f4b0b0520a628804cb463503b" + sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da url: "https://pub.dev" source: hosted - version: "2.0.7" + version: "2.0.17" flutter_svg: dependency: "direct main" description: name: flutter_svg - sha256: f991fdb1533c3caeee0cdc14b04f50f0c3916f0dbcbc05237ccbe4e3c6b93f3f + sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.0.10+1" flutter_test: dependency: "direct dev" description: flutter @@ -313,26 +337,26 @@ packages: dependency: transitive description: name: glob - sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" graphs: dependency: transitive description: name: graphs - sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2 + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.1" http: dependency: transitive description: name: http - sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482" + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "0.13.5" + version: "1.2.1" http_multi_server: dependency: transitive description: @@ -353,50 +377,74 @@ packages: dependency: "direct main" description: name: image_picker - sha256: f98d76672d309c8b7030c323b3394669e122d52b307d2bbd8d06bd70f5b2aabe + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" url: "https://pub.dev" source: hosted - version: "0.8.6+1" + version: "1.1.2" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: b1cbfec0f5aef427a18eb573f5445af8c9c568626bf3388553e40c263d3f7368 + sha256: "39f2bfe497e495450c81abcd44b62f56c2a36a37a175da7d137b4454977b51b1" url: "https://pub.dev" source: hosted - version: "0.8.5+5" + version: "0.8.9+3" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - sha256: "7d319fb74955ca46d9bf7011497860e3923bb67feebcf068f489311065863899" + sha256: "869fe8a64771b7afbc99fc433a5f7be2fea4d1cb3d7c11a48b6b579eb9c797f0" url: "https://pub.dev" source: hosted - version: "2.1.10" + version: "2.2.0" image_picker_ios: dependency: transitive description: name: image_picker_ios - sha256: "39c013200046d14c58b71dc4fa3d00e425fc9c699d589136cd3ca018727c0493" + sha256: "917a5cadd67d052554cfb258595e54217de53fac5b52939426e26319a02e6297" url: "https://pub.dev" source: hosted - version: "0.8.6+6" + version: "0.8.9+2" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" image_picker_platform_interface: dependency: transitive description: name: image_picker_platform_interface - sha256: "7cef2f28f4f2fef99180f636c3d446b4ccbafd6ba0fad2adc9a80c4040f656b8" + sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" url: "https://pub.dev" source: hosted - version: "2.6.2" + version: "2.10.0" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" intl: dependency: transitive description: name: intl - sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91" + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf url: "https://pub.dev" source: hosted - version: "0.17.0" + version: "0.19.0" io: dependency: transitive description: @@ -409,26 +457,50 @@ packages: dependency: transitive description: name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.7.1" json_annotation: dependency: "direct main" description: name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted - version: "4.8.1" + version: "4.9.0" json_serializable: dependency: "direct dev" description: name: json_serializable - sha256: "43793352f90efa5d8b251893a63d767b2f7c833120e3cc02adad55eefec04dc7" + sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b + url: "https://pub.dev" + source: hosted + version: "6.8.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" url: "https://pub.dev" source: hosted - version: "6.6.2" + version: "10.0.4" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + url: "https://pub.dev" + source: hosted + version: "3.0.3" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" lints: dependency: transitive description: @@ -441,42 +513,42 @@ packages: dependency: transitive description: name: logging - sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" matcher: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.12.0" mime: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" package_config: dependency: transitive description: @@ -489,10 +561,10 @@ packages: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.9.0" path_parsing: dependency: transitive description: @@ -505,74 +577,74 @@ packages: dependency: "direct main" description: name: path_provider - sha256: dcea5feb97d8abf90cab9e9030b497fb7c3cbf26b7a1fe9e3ef7dcb0a1ddec95 + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 url: "https://pub.dev" source: hosted - version: "2.0.12" + version: "2.1.3" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: a776c088d671b27f6e3aa8881d64b87b3e80201c64e8869b811325de7a76c15e + sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" url: "https://pub.dev" source: hosted - version: "2.0.22" + version: "2.2.2" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "62a68e7e1c6c459f9289859e2fae58290c981ce21d1697faf54910fe1faa4c74" + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.3.2" path_provider_linux: dependency: transitive description: name: path_provider_linux - sha256: ab0987bf95bc591da42dffb38c77398fc43309f0b9b894dcc5d6f40c4b26c379 + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "2.2.1" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - sha256: f0abc8ebd7253741f05488b4813d936b4d07c6bae3e86148a09e342ee4b08e76 + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.1.2" path_provider_windows: dependency: transitive description: name: path_provider_windows - sha256: bcabbe399d4042b8ee687e17548d5d3f527255253b4a639f5f8d2094a9c2b45c + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.2.1" petitparser: dependency: transitive description: name: petitparser - sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4" + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 url: "https://pub.dev" source: hosted - version: "5.1.0" + version: "6.0.2" platform: dependency: transitive description: name: platform - sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.4" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.8" pool: dependency: transitive description: @@ -593,18 +665,18 @@ packages: dependency: transitive description: name: pub_semver - sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17" + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" pubspec_parse: dependency: transitive description: name: pubspec_parse - sha256: "75f6614d6dde2dc68948dffbaa4fe5dae32cd700eb9fb763fe11dfb45a3c4d0a" + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.3" quiver: dependency: transitive description: @@ -617,82 +689,82 @@ packages: dependency: "direct main" description: name: responsive_grid_list - sha256: "682377659158e46ac7f6b4383b191bfd34ef2c5616046518b58721be370de723" + sha256: e6cd1754240795cb8b08a4520c9eb5d28856ecf71de5fe6e64535a68c0913563 url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.4.0" shared_preferences: dependency: "direct main" description: name: shared_preferences - sha256: "5949029e70abe87f75cfe59d17bf5c397619c4b74a099b10116baeb34786fad9" + sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" url: "https://pub.dev" source: hosted - version: "2.0.17" + version: "2.2.2" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "955e9736a12ba776bdd261cf030232b30eadfcd9c79b32a3250dd4a494e8c8f7" + sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" url: "https://pub.dev" source: hosted - version: "2.0.15" + version: "2.2.1" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "1ffa239043ab8baf881ec3094a3c767af9d10399b2839020b9e4d44c0bb23951" + sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.3.5" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - sha256: f8ea038aa6da37090093974ebdcf4397010605fd2ff65c37a66f9d28394cb874 + sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.3.2" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - sha256: da9431745ede5ece47bc26d5d73a9d3c6936ef6945c101a5aca46f62e52c1cf3 + sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.3.2" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - sha256: a4b5bc37fe1b368bbc81f953197d55e12f49d0296e7e412dfe2d2d77d6929958 + sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" url: "https://pub.dev" source: hosted - version: "2.0.4" + version: "2.3.0" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - sha256: "5eaf05ae77658d3521d0e993ede1af962d4b326cd2153d312df716dc250f00c9" + sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.3.2" shelf: dependency: transitive description: name: shelf - sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8 + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" sky_engine: dependency: transitive description: flutter @@ -702,42 +774,42 @@ packages: dependency: transitive description: name: source_gen - sha256: "373f96cf5a8744bc9816c1ff41cf5391bbdbe3d7a96fe98c622b6738a8a7bd33" + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.5.0" source_helper: dependency: transitive description: name: source_helper - sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f" + sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" url: "https://pub.dev" source: hosted - version: "1.3.3" + version: "1.3.4" source_span: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" stream_transform: dependency: transitive description: @@ -766,18 +838,18 @@ packages: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.7.0" timezone: dependency: transitive description: name: timezone - sha256: "57b35f6e8ef731f18529695bffc62f92c6189fac2e52c12d478dec1931afb66e" + sha256: a6ccda4a69a442098b602c44e61a1e2b4bf6f5516e875bbf0f427d5df14745d5 url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.9.3" timing: dependency: transitive description: @@ -790,34 +862,34 @@ packages: dependency: transitive description: name: typed_data - sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: ea8d3fc7b2e0f35de38a7465063ecfcf03d8217f7962aa2a6717132cb5d43a79 + sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" url: "https://pub.dev" source: hosted - version: "1.1.5" + version: "1.1.11+1" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: a5eaa5d19e123ad4f61c3718ca1ed921c4e6254238d9145f82aa214955d9aced + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da url: "https://pub.dev" source: hosted - version: "1.1.5" + version: "1.1.11+1" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: "15edc42f7eaa478ce854eaf1fbb9062a899c0e4e56e775dd73b7f4709c97c4ca" + sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" url: "https://pub.dev" source: hosted - version: "1.1.5" + version: "1.1.11+1" vector_math: dependency: transitive description: @@ -826,30 +898,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + url: "https://pub.dev" + source: hosted + version: "14.2.1" watcher: dependency: transitive description: name: watcher - sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: ca49c0bc209c687b887f30527fb6a9d80040b072cc2990f34b9bec3e7663101b + sha256: "1d8e795e2a8b3730c41b8a98a2dff2e0fb57ae6f0764a1c46ec5915387d257b2" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.4.4" win32: dependency: transitive description: name: win32 - sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46 + sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "5.3.0" xdg_directories: dependency: transitive description: @@ -862,18 +950,18 @@ packages: dependency: transitive description: name: xml - sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5" + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 url: "https://pub.dev" source: hosted - version: "6.2.2" + version: "6.5.0" yaml: dependency: transitive description: name: yaml - sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370" + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" sdks: - dart: ">=2.19.0 <3.0.0" - flutter: ">=3.7.0-0" + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.19.0" diff --git a/pubspec.yaml b/pubspec.yaml index 8263843..577c57d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,10 +15,10 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 2.0.1+4 +version: 2.1.0+5 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: '>=3.3.4 <4.0.0' # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -30,25 +30,23 @@ dependencies: flutter: sdk: flutter - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 - path_provider: ^2.0.12 - image_picker: ^0.8.5+3 - flutter_svg: ^2.0.5 - json_annotation: ^4.8.1 + path_provider: ^2.1.3 + image_picker: ^1.1.2 + flutter_svg: ^2.0.10+1 + json_annotation: ^4.9.0 flutter_localizations: sdk: flutter - background_fetch: ^1.1.5 - flutter_local_notifications: ^9.9.1 + flutter_local_notifications: ^17.1.2 shared_preferences: ^2.0.17 responsive_grid_list: ^1.3.0 + ## Static version for F-Droid deploy + background_fetch: 1.3.5 + dev_dependencies: - build_runner: ^2.3.3 - json_serializable: ^6.6.2 + build_runner: ^2.4.11 + json_serializable: ^6.8.0 flutter_test: sdk: flutter @@ -107,11 +105,3 @@ flutter: - asset: assets/NotoSans-Bold.ttf - asset: assets/NotoSans-BoldItalic.ttf style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages From 19e4f85a9c037f90821551f0659b935001b54b17 Mon Sep 17 00:00:00 2001 From: NAVAL Date: Fri, 21 Jun 2024 23:05:02 +0200 Subject: [PATCH 2/4] fix: await async functions --- README.md | 2 +- lib/data/garden.dart | 9 +++------ lib/l10n/app_es.arb | 2 +- lib/screens/care_plant.dart | 4 ++-- lib/screens/home_page.dart | 2 +- lib/screens/manage_plant.dart | 32 ++++++++++---------------------- lib/screens/settings.dart | 2 +- lib/utils/random.dart | 10 ++++++++++ metadata/en-US/changelogs/5.txt | 6 ++++++ metadata/es/changelogs/5.txt | 6 ++++++ 10 files changed, 41 insertions(+), 34 deletions(-) create mode 100644 lib/utils/random.dart create mode 100644 metadata/en-US/changelogs/5.txt create mode 100644 metadata/es/changelogs/5.txt diff --git a/README.md b/README.md index 2db6b22..ea98659 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ If your device is among those affected and notifications are not displayed, plea ### Languages -Florae is currently translated into the following languages: `English`, `Español` and `Français` thanks to Samuel Pernet. +Florae is currently translated into the following languages: `English`, `Español`, `Français`, `Nederlands`, `中文`, `Русский` and `Arabic`. If you wish to contribute to Florae by adding a new language, just include the translation file in [`lib/l10n`](lib/l10n). I will be happy to accept your pull request. diff --git a/lib/data/garden.dart b/lib/data/garden.dart index b4ac789..b149e7f 100644 --- a/lib/data/garden.dart +++ b/lib/data/garden.dart @@ -48,8 +48,7 @@ class Garden { Future deletePlant(Plant plant) async { List allPlants = await getAllPlants(); - var plantIndex = - allPlants.indexWhere((element) => element.name == plant.name); + var plantIndex = allPlants.indexWhere((element) => element.id == plant.id); if (plantIndex != -1) { allPlants.removeAt(plantIndex); @@ -65,11 +64,9 @@ class Garden { Future updatePlant(Plant plant) async { List allPlants = await getAllPlants(); - var plantIndex = - allPlants.indexWhere((element) => element.name == plant.name); + var plantIndex = allPlants.indexWhere((element) => element.id == plant.id); if (plantIndex != -1) { - allPlants[allPlants.indexWhere((element) => element.name == plant.name)] = - plant; + allPlants[plantIndex] = plant; String jsonPlants = jsonEncode(allPlants); await store.setString("plants", jsonPlants); diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 7089358..6841fff 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -51,7 +51,7 @@ "testNotificationTitle": "Notificación de prueba de Florae", "testNotificationBody": "Esto es un mensaje de prueba", "aboutFloraeButton": "Sobre Florae", - "notificationInfo": "El periodo entre notificaciones se restablecerá cuando accedas a la aplicación.\n\nTen en cuenta que algunos fabricantes aplican optimizaciones de batería muy agresivas, que pueden provocar que las notificaciones no funcionen adecuadamente.", + "notificationInfo": "El periodo entre notificaciones se restablecerá cuando accedas a la aplicación.\n\nTen en cuenta que algunos fabricantes aplican optimizaciones de batería muy agresivas que pueden provocar que las notificaciones no funcionen adecuadamente.", "careNotificationTitle": "Hay plantas que requieren atención", "careNotificationName": "Alertas de cuidados", "careNotificationDescription": "Recibe notificaciones cuando tus plantas te necesiten", diff --git a/lib/screens/care_plant.dart b/lib/screens/care_plant.dart index 5c1954d..182a471 100644 --- a/lib/screens/care_plant.dart +++ b/lib/screens/care_plant.dart @@ -66,7 +66,7 @@ class _CarePlantScreen extends State { TextButton( child: Text(AppLocalizations.of(context)!.yes), onPressed: () async { - garden.deletePlant(plant); + await garden.deletePlant(plant); Navigator.popUntil(context, ModalRoute.withName('/')); }, @@ -249,7 +249,7 @@ class _CarePlantScreen extends State { } }); - garden.updatePlant(plant); + await garden.updatePlant(plant); Navigator.of(context).pop(); } }, diff --git a/lib/screens/home_page.dart b/lib/screens/home_page.dart index 359707f..48332f8 100644 --- a/lib/screens/home_page.dart +++ b/lib/screens/home_page.dart @@ -429,7 +429,7 @@ class _MyHomePageState extends State { c.effected = DateTime.now(); } } - garden.updatePlant(p); + await garden.updatePlant(p); } setState(() { diff --git a/lib/screens/manage_plant.dart b/lib/screens/manage_plant.dart index 89b3f56..95adf18 100644 --- a/lib/screens/manage_plant.dart +++ b/lib/screens/manage_plant.dart @@ -1,5 +1,4 @@ import 'dart:io'; -import 'dart:math'; import 'package:florae/data/default.dart'; import 'package:florae/data/plant.dart'; @@ -14,6 +13,7 @@ import 'package:intl/intl.dart'; import '../data/care.dart'; import '../main.dart'; +import '../utils/random.dart'; class ManagePlantScreen extends StatefulWidget { const ManagePlantScreen( @@ -273,14 +273,11 @@ class _ManagePlantScreen extends State { child: Column(children: [ TextFormField( controller: nameController, - validator: (value) { - if (widget.update) { - return null; - } - if (value == null || value.isEmpty) { + validator: (name) { + if (name == null || name.isEmpty) { return AppLocalizations.of(context)!.emptyError; } - if (findPlant(value) != null) { + if (_plantExist(name)) { return AppLocalizations.of(context)!.conflictError; } return null; @@ -368,13 +365,14 @@ class _ManagePlantScreen extends State { floatingActionButton: FloatingActionButton.extended( onPressed: () async { String fileName = ""; + String generatedId = generateRandomString(10); if (_formKey.currentState!.validate()) { if (_image != null) { final Directory directory = await getExternalStorageDirectory() ?? await getApplicationDocumentsDirectory(); fileName = directory.path + "/" + - generateRandomString(10) + + generatedId + p.extension(_image!.path); _image!.saveTo(fileName); } @@ -384,7 +382,7 @@ class _ManagePlantScreen extends State { final newPlant = Plant( id: widget.plant != null ? widget.plant!.id - : DateTime.now().microsecondsSinceEpoch, + : generatedId.hashCode, name: nameController.text, createdAt: _planted, description: descriptionController.text, @@ -407,7 +405,7 @@ class _ManagePlantScreen extends State { } }); - garden.addOrUpdatePlant(newPlant); + await garden.addOrUpdatePlant(newPlant); Navigator.popUntil(context, ModalRoute.withName('/')); } @@ -419,22 +417,12 @@ class _ManagePlantScreen extends State { ); } - String generateRandomString(int length) { - const _chars = - 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890'; - Random _rnd = Random(); - - return String.fromCharCodes(Iterable.generate( - length, (_) => _chars.codeUnitAt(_rnd.nextInt(_chars.length)))); - } _loadPlants() async { List allPlants = await garden.getAllPlants(); - setState(() => _plants = allPlants); } - Plant? findPlant(String name) => _plants - .cast() - .firstWhere((plant) => plant.name == name, orElse: () => null); + bool _plantExist(String name) => _plants.contains((plant) => plant.name == name); + } diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart index a3f37a5..fb635ca 100644 --- a/lib/screens/settings.dart +++ b/lib/screens/settings.dart @@ -155,7 +155,7 @@ class _SettingsScreen extends State { showAboutDialog( context: context, applicationName: 'Florae', - applicationVersion: '1.0.0', + applicationVersion: '2.1.0', applicationLegalese: '© Naval Alcalá', ); }), diff --git a/lib/utils/random.dart b/lib/utils/random.dart new file mode 100644 index 0000000..2a9eb8b --- /dev/null +++ b/lib/utils/random.dart @@ -0,0 +1,10 @@ +import 'dart:math'; + +String generateRandomString(int length) { + const _chars = + 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890'; + Random _rnd = Random(); + + return String.fromCharCodes(Iterable.generate( + length, (_) => _chars.codeUnitAt(_rnd.nextInt(_chars.length)))); +} \ No newline at end of file diff --git a/metadata/en-US/changelogs/5.txt b/metadata/en-US/changelogs/5.txt new file mode 100644 index 0000000..7c3ef77 --- /dev/null +++ b/metadata/en-US/changelogs/5.txt @@ -0,0 +1,6 @@ +* New Material 3 themes added +* Now you can see in full screen the image of the plant. +* Now it is possible to change the plant name. +* Adapted plant grid for large screens +* Fixed a problem with displaying the age of the plant +* New languages added \ No newline at end of file diff --git a/metadata/es/changelogs/5.txt b/metadata/es/changelogs/5.txt new file mode 100644 index 0000000..a3ebd0c --- /dev/null +++ b/metadata/es/changelogs/5.txt @@ -0,0 +1,6 @@ +* Se añaden nuevos temas Material 3. +* Ahora puedes ver en pantalla completa la foto de tu planta. +* Ahora es posible cambiar el nombre de la planta. +* Se mejora la disposición de elementos para pantallas grandes. +* Se corrige un problema al mostrar la edad de la planta. +* Se añaden nuevos idiomas. \ No newline at end of file From 42ce9fab198c58bac2dfcc20abef248b584f1e04 Mon Sep 17 00:00:00 2001 From: NAVAL Date: Fri, 21 Jun 2024 23:11:15 +0200 Subject: [PATCH 3/4] make: release 3 --- lib/screens/settings.dart | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart index fb635ca..5e79d57 100644 --- a/lib/screens/settings.dart +++ b/lib/screens/settings.dart @@ -155,7 +155,7 @@ class _SettingsScreen extends State { showAboutDialog( context: context, applicationName: 'Florae', - applicationVersion: '2.1.0', + applicationVersion: '3.0.0', applicationLegalese: '© Naval Alcalá', ); }), diff --git a/pubspec.yaml b/pubspec.yaml index 577c57d..fd12470 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 2.1.0+5 +version: 3.0.0+5 environment: sdk: '>=3.3.4 <4.0.0' From 6447df3a1430ee12535994c001beb59a3c7e21f6 Mon Sep 17 00:00:00 2001 From: NAVAL Date: Sat, 22 Jun 2024 18:35:05 +0200 Subject: [PATCH 4/4] docs: update feature --- metadata/en-US/images/featureGraphic.png | Bin 8621 -> 6206 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/metadata/en-US/images/featureGraphic.png b/metadata/en-US/images/featureGraphic.png index 1ff5e67c2023a9228098b963f3f8b08c018fb759..137f44a45bfc67ae82101969a3cfb1b9000fa727 100644 GIT binary patch literal 6206 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 literal 8621 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 OfOIttG>R|V-Tx1>7X>N+