diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c58ffa5..23745efc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## Beta 0.7.2 + +* Added themes, closes [#105](https://github.com/flow-mn/flow/issues/105) +* Add transaction description, type, and lat/long for CSV exports, closes [#203](https://github.com/flow-mn/flow/issues/203) + +## Beta 0.7.1 + +* Fixed transfer transactions + ## Beta 0.7.0 * Added an option to choose location on a map diff --git a/assets/l10n/en_IN.json b/assets/l10n/en_IN.json index 0c1e0c3f..a8c262a9 100644 --- a/assets/l10n/en_IN.json +++ b/assets/l10n/en_IN.json @@ -146,11 +146,10 @@ "preferences.primaryCurrency": "Primary currency", "preferences.language": "Language", "preferences.language.choose": "Select a language", - "preferences.themeMode": "Theme", - "preferences.themeMode.choose": "Select a theme", - "preferences.themeMode.light": "Light", - "preferences.themeMode.dark": "Dark", - "preferences.themeMode.system": "Auto (system)", + "preferences.theme": "Theme", + "preferences.theme.choose": "Select a theme", + "preferences.theme.light": "Light", + "preferences.theme.dark": "Dark", "preferences.numpad": "Numpad", "preferences.numpad.layout": "Numpad layout", "preferences.numpad.layout.classic": "Classic", @@ -302,15 +301,19 @@ "enum.CSVHeadersV1": "CSV Headers", "enum.CSVHeadersV1@uuid": "ID", "enum.CSVHeadersV1@title": "Title", + "enum.CSVHeadersV1@notes": "Notes", "enum.CSVHeadersV1@amount": "Amount", "enum.CSVHeadersV1@currency": "Currency", "enum.CSVHeadersV1@account": "Account", "enum.CSVHeadersV1@accountUuid": "Account ID", "enum.CSVHeadersV1@category": "Category", "enum.CSVHeadersV1@categoryUuid": "Category ID", + "enum.CSVHeadersV1@type": "Type", "enum.CSVHeadersV1@subtype": "Transaction class", "enum.CSVHeadersV1@createdDate": "Created date", "enum.CSVHeadersV1@transactionDate": "Transaction date", + "enum.CSVHeadersV1@latitude": "Latitude", + "enum.CSVHeadersV1@longitude": "Longitude", "enum.CSVHeadersV1@extra": "Extra (JSON)", "enum.ImportV1Progress@waitingConfirmation": "Waiting for confirmation", diff --git a/assets/l10n/en_US.json b/assets/l10n/en_US.json index 0c1e0c3f..a8c262a9 100644 --- a/assets/l10n/en_US.json +++ b/assets/l10n/en_US.json @@ -146,11 +146,10 @@ "preferences.primaryCurrency": "Primary currency", "preferences.language": "Language", "preferences.language.choose": "Select a language", - "preferences.themeMode": "Theme", - "preferences.themeMode.choose": "Select a theme", - "preferences.themeMode.light": "Light", - "preferences.themeMode.dark": "Dark", - "preferences.themeMode.system": "Auto (system)", + "preferences.theme": "Theme", + "preferences.theme.choose": "Select a theme", + "preferences.theme.light": "Light", + "preferences.theme.dark": "Dark", "preferences.numpad": "Numpad", "preferences.numpad.layout": "Numpad layout", "preferences.numpad.layout.classic": "Classic", @@ -302,15 +301,19 @@ "enum.CSVHeadersV1": "CSV Headers", "enum.CSVHeadersV1@uuid": "ID", "enum.CSVHeadersV1@title": "Title", + "enum.CSVHeadersV1@notes": "Notes", "enum.CSVHeadersV1@amount": "Amount", "enum.CSVHeadersV1@currency": "Currency", "enum.CSVHeadersV1@account": "Account", "enum.CSVHeadersV1@accountUuid": "Account ID", "enum.CSVHeadersV1@category": "Category", "enum.CSVHeadersV1@categoryUuid": "Category ID", + "enum.CSVHeadersV1@type": "Type", "enum.CSVHeadersV1@subtype": "Transaction class", "enum.CSVHeadersV1@createdDate": "Created date", "enum.CSVHeadersV1@transactionDate": "Transaction date", + "enum.CSVHeadersV1@latitude": "Latitude", + "enum.CSVHeadersV1@longitude": "Longitude", "enum.CSVHeadersV1@extra": "Extra (JSON)", "enum.ImportV1Progress@waitingConfirmation": "Waiting for confirmation", diff --git a/assets/l10n/it_IT.json b/assets/l10n/it_IT.json index 3fea1390..e84b6af1 100644 --- a/assets/l10n/it_IT.json +++ b/assets/l10n/it_IT.json @@ -146,11 +146,10 @@ "preferences.primaryCurrency": "Valuta principale", "preferences.language": "Lingua", "preferences.language.choose": "Selezionare una lingua", - "preferences.themeMode": "Tema", - "preferences.themeMode.choose": "Seleziona un tema", - "preferences.themeMode.light": "Chiaro", - "preferences.themeMode.dark": "Scuro", - "preferences.themeMode.system": "Auto (sistema)", + "preferences.theme": "Tema", + "preferences.theme.choose": "Seleziona un tema", + "preferences.theme.light": "Chiaro", + "preferences.theme.dark": "Scuro", "preferences.numpad": "Tastierino numerico", "preferences.numpad.layout": "Layout del tastierino numerico", "preferences.numpad.layout.classic": "Classico", @@ -302,15 +301,19 @@ "enum.CSVHeadersV1": "Intestazioni CSV", "enum.CSVHeadersV1@uuid": "ID", "enum.CSVHeadersV1@title": "Titolo", + "enum.CSVHeadersV1@notes": "Note", "enum.CSVHeadersV1@amount": "Importo", "enum.CSVHeadersV1@currency": "Valuta", "enum.CSVHeadersV1@account": "Conto", "enum.CSVHeadersV1@accountUuid": "ID Conto", "enum.CSVHeadersV1@category": "Categoria", "enum.CSVHeadersV1@categoryUuid": "ID Categoria", + "enum.CSVHeadersV1@type": "Tipo", "enum.CSVHeadersV1@subtype": "Classe di transazione", "enum.CSVHeadersV1@createdDate": "Data di creazione", "enum.CSVHeadersV1@transactionDate": "Data transazione", + "enum.CSVHeadersV1@latitude": "Latitudine", + "enum.CSVHeadersV1@longitude": "Longitudine", "enum.CSVHeadersV1@extra": "Extra (JSON)", "enum.ImportV1Progress@waitingConfirmation": "In attesa di conferma", diff --git a/assets/l10n/mn_MN.json b/assets/l10n/mn_MN.json index f025516a..be7312e0 100644 --- a/assets/l10n/mn_MN.json +++ b/assets/l10n/mn_MN.json @@ -146,11 +146,10 @@ "preferences.primaryCurrency": "Үндсэн валют", "preferences.language": "Хэл", "preferences.language.choose": "Хэл сонгох", - "preferences.themeMode": "Үзэмж", - "preferences.themeMode.choose": "Үзэмж сонгох", - "preferences.themeMode.light": "Гэгээлэг", - "preferences.themeMode.dark": "Харанхуй", - "preferences.themeMode.system": "Авто (систем)", + "preferences.theme": "Үзэмж", + "preferences.theme.choose": "Үзэмж сонгох", + "preferences.theme.light": "Гэгээлэг", + "preferences.theme.dark": "Харанхуй", "preferences.numpad": "Тоон товчлуур", "preferences.numpad.layout": "Тооны байрлал", "preferences.numpad.layout.classic": "Хуучны", @@ -302,15 +301,19 @@ "enum.CSVHeadersV1": "CSV Толгой", "enum.CSVHeadersV1@uuid": "Код", "enum.CSVHeadersV1@title": "Гарчиг", + "enum.CSVHeadersV1@notes": "Тэмдэглэл", "enum.CSVHeadersV1@amount": "Дүн", "enum.CSVHeadersV1@currency": "Валют", "enum.CSVHeadersV1@account": "Данс", "enum.CSVHeadersV1@accountUuid": "Дансны код", "enum.CSVHeadersV1@category": "Ангилал", "enum.CSVHeadersV1@categoryUuid": "Ангилалын код", + "enum.CSVHeadersV1@type": "Төрөл", "enum.CSVHeadersV1@subtype": "Гүйлгээний ангилал", "enum.CSVHeadersV1@createdDate": "Нэмсэн огноо ({})", "enum.CSVHeadersV1@transactionDate": "Гүйлгээ хийсэн ({})", + "enum.CSVHeadersV1@latitude": "Уртраг", + "enum.CSVHeadersV1@longitude": "Өргөрөг", "enum.CSVHeadersV1@extra": "Нэмэлт(JSON)", "enum.ImportV1Progress@waitingConfirmation": "Баталгаажуулалт хүлээж байна", diff --git a/ios/Podfile.lock b/ios/Podfile.lock index dc8b54e9..0f15eafb 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,35 +1,35 @@ PODS: - app_settings (5.1.1): - Flutter - - DKImagePickerController/Core (4.3.4): + - DKImagePickerController/Core (4.3.9): - DKImagePickerController/ImageDataManager - DKImagePickerController/Resource - - DKImagePickerController/ImageDataManager (4.3.4) - - DKImagePickerController/PhotoGallery (4.3.4): + - DKImagePickerController/ImageDataManager (4.3.9) + - DKImagePickerController/PhotoGallery (4.3.9): - DKImagePickerController/Core - DKPhotoGallery - - DKImagePickerController/Resource (4.3.4) - - DKPhotoGallery (0.0.17): - - DKPhotoGallery/Core (= 0.0.17) - - DKPhotoGallery/Model (= 0.0.17) - - DKPhotoGallery/Preview (= 0.0.17) - - DKPhotoGallery/Resource (= 0.0.17) + - DKImagePickerController/Resource (4.3.9) + - DKPhotoGallery (0.0.19): + - DKPhotoGallery/Core (= 0.0.19) + - DKPhotoGallery/Model (= 0.0.19) + - DKPhotoGallery/Preview (= 0.0.19) + - DKPhotoGallery/Resource (= 0.0.19) - SDWebImage - SwiftyGif - - DKPhotoGallery/Core (0.0.17): + - DKPhotoGallery/Core (0.0.19): - DKPhotoGallery/Model - DKPhotoGallery/Preview - SDWebImage - SwiftyGif - - DKPhotoGallery/Model (0.0.17): + - DKPhotoGallery/Model (0.0.19): - SDWebImage - SwiftyGif - - DKPhotoGallery/Preview (0.0.17): + - DKPhotoGallery/Preview (0.0.19): - DKPhotoGallery/Model - DKPhotoGallery/Resource - SDWebImage - SwiftyGif - - DKPhotoGallery/Resource (0.0.17): + - DKPhotoGallery/Resource (0.0.19): - SDWebImage - SwiftyGif - file_picker (0.0.1): @@ -44,10 +44,10 @@ PODS: - Flutter - image_picker_ios (0.0.1): - Flutter - - ObjectBox (2.0.0) + - ObjectBox (4.0.1) - objectbox_flutter_libs (0.0.1): - Flutter - - ObjectBox (= 2.0.0) + - ObjectBox (= 4.0.1) - package_info_plus (0.4.5): - Flutter - path_provider_foundation (0.0.1): @@ -55,15 +55,15 @@ PODS: - FlutterMacOS - pointer_interceptor_ios (0.0.1): - Flutter - - SDWebImage (5.18.10): - - SDWebImage/Core (= 5.18.10) - - SDWebImage/Core (5.18.10) + - SDWebImage (5.19.7): + - SDWebImage/Core (= 5.19.7) + - SDWebImage/Core (5.19.7) - share_plus (0.0.1): - Flutter - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS - - SwiftyGif (5.4.4) + - SwiftyGif (5.4.5) - url_launcher_ios (0.0.1): - Flutter @@ -123,23 +123,23 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: app_settings: 017320c6a680cdc94c799949d95b84cb69389ebc - DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac - DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 + DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c + DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655 file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069 geolocator_apple: 6cbaf322953988e009e5ecb481f07efece75c450 image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 - ObjectBox: f5319bd9ad2ea960796eff7227e86471867e9ef0 - objectbox_flutter_libs: c7748f6c6fda47d22f15c8062fb8208063fd948a - package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c + ObjectBox: 0bc4bb75eea85f6af06b369148b334c2056bbc29 + objectbox_flutter_libs: 2ce0da386c780878687c736b528ceaf371573efb + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 pointer_interceptor_ios: 508241697ff0947f853c061945a8b822463947c1 - SDWebImage: fc8f2d48bbfd72ef39d70e981bd24a3f3be53fec - share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad + SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3 + share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f + SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe PODFILE CHECKSUM: 4d14e87b7a27c32224c4ff9f8d20f1eb4a2a2798 diff --git a/lib/libobjectbox.so b/lib/libobjectbox.so deleted file mode 100755 index 4922a57f..00000000 Binary files a/lib/libobjectbox.so and /dev/null differ diff --git a/lib/main.dart b/lib/main.dart index 9302c5f7..1b18dba1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,7 +18,9 @@ import "dart:async"; import "dart:developer"; import "dart:io"; +import "dart:ui"; +import "package:dynamic_color/dynamic_color.dart"; import "package:flow/constants.dart"; import "package:flow/entity/profile.dart"; import "package:flow/entity/transaction.dart"; @@ -28,13 +30,14 @@ import "package:flow/objectbox/actions.dart"; import "package:flow/prefs.dart"; import "package:flow/routes.dart"; import "package:flow/services/exchange_rates.dart"; +import "package:flow/theme/color_themes/registry.dart"; +import "package:flow/theme/flow_color_scheme.dart"; import "package:flow/theme/theme.dart"; import "package:flutter/material.dart"; import "package:flutter_localizations/flutter_localizations.dart"; import "package:intl/intl.dart"; import "package:moment_dart/moment_dart.dart"; import "package:package_info_plus/package_info_plus.dart"; -import "package:pie_menu/pie_menu.dart"; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -80,16 +83,14 @@ class FlowState extends State { Locale _locale = FlowLocalizations.supportedLanguages.first; ThemeMode _themeMode = ThemeMode.system; + ThemeFactory _themeFactory = ThemeFactory.fromThemeName(null); + ThemeMode get themeMode => _themeMode; bool get useDarkTheme => (_themeMode == ThemeMode.system - ? (MediaQuery.platformBrightnessOf(context) == Brightness.dark) + ? (PlatformDispatcher.instance.platformBrightness == Brightness.dark) : (_themeMode == ThemeMode.dark)); - PieTheme get pieTheme { - return useDarkTheme ? pieThemeDark : pieThemeLight; - } - @override void initState() { super.initState(); @@ -98,7 +99,7 @@ class FlowState extends State { _reloadTheme(); LocalPreferences().localeOverride.addListener(_reloadLocale); - LocalPreferences().themeMode.addListener(_reloadTheme); + LocalPreferences().themeName.addListener(_reloadTheme); ObjectBox().box().query().watch().listen((event) { ObjectBox().invalidateAccountsTab(); @@ -112,34 +113,68 @@ class FlowState extends State { @override void dispose() { LocalPreferences().localeOverride.removeListener(_reloadLocale); - LocalPreferences().themeMode.removeListener(_reloadTheme); + LocalPreferences().themeName.removeListener(_reloadTheme); super.dispose(); } @override Widget build(BuildContext context) { - return MaterialApp.router( - onGenerateTitle: (context) => "appName".t(context), - localizationsDelegates: [ - GlobalMaterialLocalizations.delegate, - if (flowDebugMode || Platform.isIOS) - GlobalCupertinoLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - FlowLocalizations.delegate, - ], - supportedLocales: FlowLocalizations.supportedLanguages, - locale: _locale, - routerConfig: router, - theme: lightTheme, - darkTheme: darkTheme, - themeMode: _themeMode, - debugShowCheckedModeBanner: false, + return DynamicColorBuilder( + builder: (dynamicLight, dynamicDark) { + return MaterialApp.router( + onGenerateTitle: (context) => "appName".t(context), + localizationsDelegates: [ + GlobalMaterialLocalizations.delegate, + if (flowDebugMode || Platform.isIOS) + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + FlowLocalizations.delegate, + ], + supportedLocales: FlowLocalizations.supportedLanguages, + locale: _locale, + routerConfig: router, + theme: _themeFactory.materialTheme, + darkTheme: _themeFactory.materialTheme, + themeMode: _themeMode, + debugShowCheckedModeBanner: false, + ); + }, ); } void _reloadTheme() { + final ThemeMode legacyThemeMode = + LocalPreferences().themeMode.value ?? ThemeMode.system; + final String? themeName = LocalPreferences().themeName.value; + + log("[Theme] Reloading theme $themeName"); + + ({FlowColorScheme scheme, ThemeMode mode})? experimentalTheme = + getTheme(themeName); + + if (experimentalTheme == null) { + final bool fallbackToDarkTheme = + switch ((legacyThemeMode, useDarkTheme)) { + (ThemeMode.system, true) => true, + (ThemeMode.system, false) => true, + (ThemeMode.dark, _) => true, + (ThemeMode.light, _) => false + }; + + log("[Theme] Didn't find theme for $themeName"); + unawaited( + LocalPreferences() + .themeName + .set((fallbackToDarkTheme ? darkThemes : lightThemes).keys.first), + ); + } + setState(() { - _themeMode = LocalPreferences().themeMode.value ?? _themeMode; + _themeMode = experimentalTheme?.mode ?? legacyThemeMode; + _themeFactory = ThemeFactory( + experimentalTheme?.scheme ?? + (useDarkTheme ? electricLavender : shadeOfViolet), + ); }); } diff --git a/lib/objectbox/actions.dart b/lib/objectbox/actions.dart index a08bcfe9..b9750ad4 100644 --- a/lib/objectbox/actions.dart +++ b/lib/objectbox/actions.dart @@ -114,7 +114,7 @@ extension MainActions on ObjectBox { if (ignoreTransfers && transaction.isTransfer) continue; final String categoryUuid = - transaction.category.target?.uuid ?? Uuid.NAMESPACE_NIL; + transaction.category.target?.uuid ?? Namespace.nil.value; flow[categoryUuid] ??= MoneyFlow( associatedData: transaction.category.target, @@ -149,7 +149,7 @@ extension MainActions on ObjectBox { if (ignoreTransfers && transaction.isTransfer) continue; final String accountUuid = - transaction.account.target?.uuid ?? Uuid.NAMESPACE_NIL; + transaction.account.target?.uuid ?? Namespace.nil.value; flow[accountUuid] ??= MoneyFlow( associatedData: transaction.account.target, @@ -158,7 +158,7 @@ extension MainActions on ObjectBox { } assert( - !flow.containsKey(Uuid.NAMESPACE_NIL), + !flow.containsKey(Namespace.nil.value), "There is no way you've managed to make a transaction without an account", ); @@ -314,7 +314,7 @@ extension TransactionActions on Transaction { final Query query = ObjectBox() .box() .query(Transaction_.uuid - .equals(transfer.relatedTransactionUuid ?? Uuid.NAMESPACE_NIL)) + .equals(transfer.relatedTransactionUuid ?? Namespace.nil.value)) .build(); try { @@ -336,7 +336,7 @@ extension TransactionActions on Transaction { final Query relatedTransactionQuery = ObjectBox() .box() .query(Transaction_.uuid - .equals(transfer.relatedTransactionUuid ?? Uuid.NAMESPACE_NIL)) + .equals(transfer.relatedTransactionUuid ?? Namespace.nil.value)) .build(); final Transaction? relatedTransaction = diff --git a/lib/prefs.dart b/lib/prefs.dart index d407a679..c0205c23 100644 --- a/lib/prefs.dart +++ b/lib/prefs.dart @@ -10,6 +10,7 @@ import "package:flow/entity/category.dart"; import "package:flow/entity/transaction.dart"; import "package:flow/objectbox.dart"; import "package:flow/objectbox/objectbox.g.dart"; +import "package:flow/theme/color_themes/registry.dart"; import "package:flutter/material.dart"; import "package:intl/intl.dart"; import "package:latlong2/latlong.dart"; @@ -60,7 +61,6 @@ class LocalPreferences { late final BoolSettingsEntry completedInitialSetup; - late final ThemeModeSettingsEntry themeMode; late final LocaleSettingsEntry localeOverride; /// Whether the user uses only one currency across accounts @@ -76,6 +76,9 @@ class LocalPreferences { late final JsonSettingsEntry lastKnownGeo; + late final ThemeModeSettingsEntry themeMode; + late final PrimitiveSettingsEntry themeName; + LocalPreferences._internal(this._prefs) { primaryCurrency = PrimitiveSettingsEntry( key: "flow.primaryCurrency", @@ -127,11 +130,6 @@ class LocalPreferences { initialValue: false, ); - themeMode = ThemeModeSettingsEntry( - key: "flow.themeMode", - preferences: _prefs, - initialValue: ThemeMode.system, - ); localeOverride = LocaleSettingsEntry( key: "flow.localeOverride", preferences: _prefs, @@ -175,6 +173,17 @@ class LocalPreferences { toJson: (data) => data.toJson(), ); + themeMode = ThemeModeSettingsEntry( + key: "flow.themeMode", + preferences: _prefs, + initialValue: ThemeMode.system, + ); + themeName = PrimitiveSettingsEntry( + key: "flow.themeName", + preferences: _prefs, + initialValue: lightThemes.keys.first, + ); + updateTransitiveProperties(); } diff --git a/lib/routes.dart b/lib/routes.dart index 3714da1a..5358faba 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -15,6 +15,7 @@ import "package:flow/routes/import_wizard/v1.dart"; import "package:flow/routes/preferences/button_order_preferences_page.dart"; import "package:flow/routes/preferences/home_tab_preferences.dart"; import "package:flow/routes/preferences/numpad_preferences_page.dart"; +import "package:flow/routes/preferences/theme_preferences_page.dart"; import "package:flow/routes/preferences/transaction_geo_preferences_page.dart"; import "package:flow/routes/preferences/transfer_preferences_page.dart"; import "package:flow/routes/preferences_page.dart"; @@ -169,6 +170,10 @@ final router = GoRouter( path: "transactionGeo", builder: (context, state) => const TransactionGeoPreferencesPage(), ), + GoRoute( + path: "theme", + builder: (context, state) => const ThemePreferencesPage(), + ), ], ), GoRoute( diff --git a/lib/routes/home/profile_tab.dart b/lib/routes/home/profile_tab.dart index e7f7652c..f7012205 100644 --- a/lib/routes/home/profile_tab.dart +++ b/lib/routes/home/profile_tab.dart @@ -1,8 +1,12 @@ +import "dart:async"; + import "package:flow/constants.dart"; import "package:flow/l10n/extensions.dart"; import "package:flow/objectbox.dart"; +import "package:flow/prefs.dart"; import "package:flow/services/exchange_rates.dart"; import "package:flow/sync/import.dart"; +import "package:flow/theme/color_themes/registry.dart"; import "package:flow/theme/theme.dart"; import "package:flow/utils/utils.dart"; import "package:flow/widgets/general/button.dart"; @@ -11,6 +15,7 @@ import "package:flow/widgets/home/prefs/profile_card.dart"; import "package:flutter/material.dart"; import "package:go_router/go_router.dart"; import "package:material_symbols_icons/symbols.dart"; +import "package:shared_preferences/shared_preferences.dart"; import "package:simple_icons/simple_icons.dart"; class ProfileTab extends StatefulWidget { @@ -22,6 +27,10 @@ class ProfileTab extends StatefulWidget { class _ProfileTabState extends State { bool _debugDbBusy = false; + bool _debugPrefsBusy = false; + + Timer? _debugDiscoTimer; + int _debugDiscoIndex = 0; @override Widget build(BuildContext context) { @@ -74,6 +83,13 @@ class _ProfileTabState extends State { if (flowDebugMode) ...[ const SizedBox(height: 32.0), const ListHeader("Debug options"), + ListTile( + title: _debugDiscoTimer == null + ? const Text("Turn on disco") + : const Text("Turn off disco"), + leading: const Icon(Symbols.party_mode_rounded), + onTap: toggleDisco, + ), ListTile( title: const Text("Populate objectbox"), leading: const Icon(Symbols.adb_rounded), @@ -90,6 +106,11 @@ class _ProfileTabState extends State { onTap: () => resetDatabase(), leading: const Icon(Symbols.adb_rounded), ), + ListTile( + title: Text("Clear Shared Preferences"), + onTap: () => resetPrefs(), + leading: const Icon(Symbols.adb_rounded), + ), ListTile( title: const Text("Jump to setup page"), onTap: () => context.pushReplacement("/setup"), @@ -123,6 +144,30 @@ class _ProfileTabState extends State { ); } + void toggleDisco() { + if (_debugDiscoTimer != null) { + _debugDiscoTimer!.cancel(); + _debugDiscoTimer = null; + } else { + _debugDiscoTimer = Timer.periodic(const Duration(seconds: 1), (timer) { + try { + final newThemeName = darkThemes.keys.elementAt(_debugDiscoIndex++); + + unawaited( + LocalPreferences().themeName.set(newThemeName), + ); + } catch (e) { + timer.cancel(); + _debugDiscoTimer = null; + if (mounted) { + setState(() {}); + } + } + }); + } + setState(() {}); + } + void resetDatabase() async { if (_debugDbBusy) return; @@ -160,6 +205,44 @@ class _ProfileTabState extends State { } } + void resetPrefs() async { + if (_debugPrefsBusy) return; + + final bool? confirm = await showDialog( + context: context, + builder: (context) => AlertDialog.adaptive( + title: const Text("[dev] Clear Shared Preferences?"), + actions: [ + Button( + onTap: () => context.pop(true), + child: const Text("Confirm clear"), + ), + Button( + onTap: () => context.pop(false), + child: const Text("Cancel"), + ), + ], + ), + ); + + setState(() { + _debugPrefsBusy = true; + }); + + try { + if (confirm == true) { + final instance = await SharedPreferences.getInstance(); + await instance.clear(); + } + } finally { + _debugPrefsBusy = false; + + if (mounted) { + setState(() {}); + } + } + } + void clearExchangeRatesCache() { ExchangeRatesService().debugClearCache(); } diff --git a/lib/routes/home_page.dart b/lib/routes/home_page.dart index 733a2a66..183b340b 100644 --- a/lib/routes/home_page.dart +++ b/lib/routes/home_page.dart @@ -2,21 +2,20 @@ import "dart:async"; import "package:flow/entity/account.dart"; import "package:flow/entity/transaction.dart"; -import "package:flow/main.dart"; import "package:flow/objectbox.dart"; import "package:flow/prefs.dart"; import "package:flow/routes/home/accounts_tab.dart"; import "package:flow/routes/home/home_tab.dart"; import "package:flow/routes/home/profile_tab.dart"; import "package:flow/routes/home/stats_tab.dart"; +import "package:flow/theme/theme.dart"; import "package:flow/utils/shortcut.dart"; import "package:flow/widgets/home/navbar.dart"; import "package:flow/widgets/home/navbar/new_transaction_button.dart"; import "package:flutter/material.dart" hide Flow; import "package:flutter/services.dart"; -import "package:go_router/go_router.dart"; - import "package:flutter_floating_bottom_bar/flutter_floating_bottom_bar.dart"; +import "package:go_router/go_router.dart"; import "package:pie_menu/pie_menu.dart"; class HomePage extends StatefulWidget { @@ -77,7 +76,7 @@ class _HomePageState extends State child: Focus( autofocus: true, child: PieCanvas( - theme: Flow.of(context).pieTheme, + theme: context.pieTheme, child: BottomBar( width: double.infinity, offset: 16.0, diff --git a/lib/routes/preferences/theme_preferences/theme_entry.dart b/lib/routes/preferences/theme_preferences/theme_entry.dart new file mode 100644 index 00000000..21c8ae84 --- /dev/null +++ b/lib/routes/preferences/theme_preferences/theme_entry.dart @@ -0,0 +1,42 @@ +import "package:flow/theme/flow_color_scheme.dart"; +import "package:flutter/material.dart"; + +class ThemeEntry extends StatelessWidget { + final MapEntry entry; + final String currentTheme; + final void Function(String?) handleChange; + + const ThemeEntry({ + super.key, + required this.entry, + required this.currentTheme, + required this.handleChange, + }); + + @override + Widget build(BuildContext context) { + return RadioListTile.adaptive( + title: Text(entry.value.name), + value: entry.key, + groupValue: currentTheme, + onChanged: handleChange, + secondary: AspectRatio( + aspectRatio: 1.0, + child: Container( + margin: EdgeInsets.all(4.0), + decoration: BoxDecoration( + color: entry.value.colorScheme.secondary, + borderRadius: BorderRadius.circular(8.0), + ), + alignment: Alignment.center, + child: Text( + "Aa", + style: TextStyle( + color: entry.value.colorScheme.primary, + ), + ), + ), + ), + ); + } +} diff --git a/lib/routes/preferences/theme_preferences_page.dart b/lib/routes/preferences/theme_preferences_page.dart new file mode 100644 index 00000000..d7ce187d --- /dev/null +++ b/lib/routes/preferences/theme_preferences_page.dart @@ -0,0 +1,97 @@ +import "package:flow/l10n/extensions.dart"; +import "package:flow/prefs.dart"; +import "package:flow/routes/preferences/theme_preferences/theme_entry.dart"; +import "package:flow/theme/color_themes/registry.dart"; +import "package:flutter/material.dart"; + +class ThemePreferencesPage extends StatefulWidget { + const ThemePreferencesPage({super.key}); + + @override + State createState() => _ThemePreferencesPageState(); +} + +class _ThemePreferencesPageState extends State + with SingleTickerProviderStateMixin { + late final TabController _tabController; + + bool busy = false; + + @override + void initState() { + super.initState(); + _tabController = TabController( + length: 2, + vsync: this, + ); + } + + @override + Widget build(BuildContext context) { + final String? preferencesTheme = LocalPreferences().themeName.get(); + final String currentTheme = validateThemeName(preferencesTheme) + ? preferencesTheme! + : lightThemes.keys.first; + + return Scaffold( + appBar: AppBar( + title: Text("preferences.theme.choose".t(context)), + bottom: TabBar( + tabs: [ + Tab( + text: "preferences.theme.light".t(context), + ), + Tab( + text: "preferences.theme.dark".t(context), + ), + ], + controller: _tabController, + ), + ), + body: SafeArea( + child: TabBarView( + controller: _tabController, + children: [ + ListView( + children: lightThemes.entries + .map( + (entry) => ThemeEntry( + entry: entry, + currentTheme: currentTheme, + handleChange: handleChange, + ), + ) + .toList(), + ), + ListView( + children: darkThemes.entries + .map( + (entry) => ThemeEntry( + entry: entry, + currentTheme: currentTheme, + handleChange: handleChange, + ), + ) + .toList(), + ), + ], + ), + ), + ); + } + + void handleChange(String? name) async { + if (name == null) return; + if (busy) return; + + try { + await LocalPreferences().themeName.set(name); + } finally { + busy = false; + + if (mounted) { + setState(() {}); + } + } + } +} diff --git a/lib/routes/preferences_page.dart b/lib/routes/preferences_page.dart index 2424bffd..89032d29 100644 --- a/lib/routes/preferences_page.dart +++ b/lib/routes/preferences_page.dart @@ -5,10 +5,10 @@ import "package:app_settings/app_settings.dart"; import "package:flow/data/upcoming_transactions.dart"; import "package:flow/l10n/flow_localizations.dart"; import "package:flow/l10n/named_enum.dart"; -import "package:flow/main.dart"; import "package:flow/prefs.dart"; import "package:flow/routes/preferences/language_selection_sheet.dart"; -import "package:flow/routes/preferences/theme_selection_sheet.dart"; +import "package:flow/theme/color_themes/registry.dart"; +import "package:flow/theme/flow_color_scheme.dart"; import "package:flow/widgets/select_currency_sheet.dart"; import "package:flutter/material.dart" hide Flow; import "package:go_router/go_router.dart"; @@ -22,13 +22,13 @@ class PreferencesPage extends StatefulWidget { } class _PreferencesPageState extends State { - bool _themeBusy = false; bool _currencyBusy = false; bool _languageBusy = false; @override Widget build(BuildContext context) { - final ThemeMode currentThemeMode = Flow.of(context).themeMode; + final FlowColorScheme currentTheme = + getTheme(LocalPreferences().themeName.get())?.scheme ?? shadeOfViolet; final UpcomingTransactionsDuration homeTabPlannedTransactionsDuration = LocalPreferences().homeTabPlannedTransactionsDuration.get() ?? @@ -55,19 +55,12 @@ class _PreferencesPageState extends State { trailing: const Icon(Symbols.chevron_right_rounded), ), ListTile( - title: Text("preferences.themeMode".t(context)), - leading: switch (currentThemeMode) { - ThemeMode.system => const Icon(Symbols.routine_rounded), - ThemeMode.dark => const Icon(Symbols.dark_mode_rounded), - ThemeMode.light => const Icon(Symbols.light_mode_rounded), - }, - subtitle: Text(switch (currentThemeMode) { - ThemeMode.system => "preferences.themeMode.system".t(context), - ThemeMode.dark => "preferences.themeMode.dark".t(context), - ThemeMode.light => "preferences.themeMode.light".t(context), - }), - onTap: () => updateTheme(), - onLongPress: () => updateTheme(ThemeMode.system), + title: Text("preferences.theme".t(context)), + leading: currentTheme.isDark + ? const Icon(Symbols.dark_mode_rounded) + : const Icon(Symbols.light_mode_rounded), + subtitle: Text(currentTheme.name), + onTap: openTheme, trailing: const Icon(Symbols.chevron_right_rounded), ), ListTile( @@ -137,36 +130,6 @@ class _PreferencesPageState extends State { ); } - void updateTheme([ThemeMode? force]) async { - if (_themeBusy) return; - - setState(() { - _themeBusy = true; - }); - - try { - final ThemeMode? selected = await showModalBottomSheet( - context: context, - builder: (context) => ThemeSelectionSheet( - currentTheme: Flow.of(context).themeMode, - ), - ); - - if (selected != null) { - await LocalPreferences().themeMode.set(selected); - } - - if (mounted) { - // Even tho the whole app state refreshes, it doesn't get refreshed - // if we switch from same ThemeMode as system from ThemeMode.system. - // So this call is necessary - setState(() {}); - } - } finally { - _themeBusy = false; - } - } - void updateLanguage() async { if (Platform.isIOS) { await LocalPreferences().localeOverride.remove().catchError((e) { @@ -261,4 +224,11 @@ class _PreferencesPageState extends State { // Rebuild to update description text if (mounted) setState(() {}); } + + void openTheme() async { + await context.push("/preferences/theme"); + + // Rebuild to update description text + if (mounted) setState(() {}); + } } diff --git a/lib/sync/export/export_v1.dart b/lib/sync/export/export_v1.dart index 9e2ab4ca..835570e6 100644 --- a/lib/sync/export/export_v1.dart +++ b/lib/sync/export/export_v1.dart @@ -59,15 +59,19 @@ Future generateCSVContentV1() async { final headers = [ CSVHeadersV1.uuid.localizedName, CSVHeadersV1.title.localizedName, + CSVHeadersV1.notes.localizedName, CSVHeadersV1.amount.localizedName, CSVHeadersV1.currency.localizedName, CSVHeadersV1.account.localizedName, CSVHeadersV1.accountUuid.localizedName, CSVHeadersV1.category.localizedName, CSVHeadersV1.categoryUuid.localizedName, + CSVHeadersV1.type.localizedName, CSVHeadersV1.subtype.localizedName, CSVHeadersV1.createdDate.localizedName, CSVHeadersV1.transactionDate.localizedName, + CSVHeadersV1.latitude.localizedName, + CSVHeadersV1.longitude.localizedName, CSVHeadersV1.extra.localizedName, ]; @@ -78,6 +82,7 @@ Future generateCSVContentV1() async { (e) => [ e.uuid, e.title ?? "", + e.description ?? "", e.amount.toStringAsFixed( numberOfDecimalsToKeep[e.currency] ??= NumberFormat.currency(name: e.currency).decimalDigits ?? 2, @@ -87,6 +92,7 @@ Future generateCSVContentV1() async { e.account.target?.uuid, e.category.target?.name, e.category.target?.uuid, + e.type.localizedName, e.transactionSubtype?.localizedName, e.createdDate.format( payload: "LLL", @@ -96,11 +102,14 @@ Future generateCSVContentV1() async { payload: "LLL", forceLocal: true, ), + e.extensions.geo?.latitude?.toString() ?? "", + e.extensions.geo?.longitude?.toString() ?? "", e.extra, ], ) .toList() ..insert(0, headers); - return const ListToCsvConverter().convert(transformed); + return const ListToCsvConverter() + .convert(transformed, convertNullTo: "", eol: "\n"); } diff --git a/lib/sync/export/headers/header_v1.dart b/lib/sync/export/headers/header_v1.dart index cb8bbd24..5dfccbb0 100644 --- a/lib/sync/export/headers/header_v1.dart +++ b/lib/sync/export/headers/header_v1.dart @@ -3,15 +3,19 @@ import "package:flow/l10n/named_enum.dart"; enum CSVHeadersV1 implements LocalizedEnum { uuid, title, + notes, amount, currency, account, accountUuid, category, categoryUuid, + type, subtype, createdDate, transactionDate, + latitude, + longitude, extra; @override diff --git a/lib/theme/color_scheme.dart b/lib/theme/color_scheme.dart deleted file mode 100644 index 18097c78..00000000 --- a/lib/theme/color_scheme.dart +++ /dev/null @@ -1,24 +0,0 @@ -part of "theme.dart"; - -const _light = ColorScheme( - brightness: Brightness.light, - primary: Color(0xFF8500a6), - onPrimary: Color(0xFFf5f6fa), - secondary: Color(0xFFF5CCFF), - onSecondary: Color(0xFF33004F), - error: Color(0xFFff4040), - onError: Color(0xFFf5f6fa), - surface: Color(0xFFF5F6FA), - onSurface: Color(0xFF0A000D), -); -const _dark = ColorScheme( - brightness: Brightness.dark, - primary: Color(0xFFF2C0FF), - onPrimary: Color(0xFF222222), - secondary: Color(0xFF111111), - onSecondary: Color(0xFFf5f6fa), - error: Color(0xFFff4040), - onError: Color(0xFFf5f6fa), - surface: Color(0xFF222222), - onSurface: Color(0xFFF5F6FA), -); diff --git a/lib/theme/color_themes/default_darks.dart b/lib/theme/color_themes/default_darks.dart new file mode 100644 index 00000000..31b75d1c --- /dev/null +++ b/lib/theme/color_themes/default_darks.dart @@ -0,0 +1,259 @@ +import "dart:ui"; + +import "package:flow/theme/flow_color_scheme.dart"; + +final FlowColorScheme electricLavender = FlowColorScheme( + name: "Electric Lavender", + isDark: true, + surface: const Color(0xff141414), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xfff2c0ff), + onPrimary: const Color(0xff141414), + secondary: const Color(0xff050505), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF97919B), + ), +); + +final FlowColorScheme pinkQuartz = FlowColorScheme( + name: "Pink Quartz", + isDark: true, + surface: const Color(0xff141414), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffffc0f4), + onPrimary: const Color(0xff141414), + secondary: const Color(0xff050505), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF97919B), + ), +); + +final FlowColorScheme cottonCandy = FlowColorScheme( + name: "Cotton Candy", + isDark: true, + surface: const Color(0xff141414), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffffc0dc), + onPrimary: const Color(0xff141414), + secondary: const Color(0xff050505), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF97919B), + ), +); + +final FlowColorScheme piglet = FlowColorScheme( + name: "Piglet", + isDark: true, + surface: const Color(0xff141414), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffffc0c5), + onPrimary: const Color(0xff141414), + secondary: const Color(0xff050505), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF97919B), + ), +); + +final FlowColorScheme simplyDelicious = FlowColorScheme( + name: "Simply Delicious", + isDark: true, + surface: const Color(0xff141414), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffffd2c0), + onPrimary: const Color(0xff141414), + secondary: const Color(0xff050505), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF97919B), + ), +); + +final FlowColorScheme creamyApricot = FlowColorScheme( + name: "Creamy Apricot", + isDark: true, + surface: const Color(0xff141414), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffffeac0), + onPrimary: const Color(0xff141414), + secondary: const Color(0xff050505), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF97919B), + ), +); + +final FlowColorScheme yellYellow = FlowColorScheme( + name: "Yell Yellow", + isDark: true, + surface: const Color(0xff141414), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xfffcffc0), + onPrimary: const Color(0xff141414), + secondary: const Color(0xff050505), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF97919B), + ), +); + +final FlowColorScheme fallGreen = FlowColorScheme( + name: "Fall Green", + isDark: true, + surface: const Color(0xff141414), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffe4ffc0), + onPrimary: const Color(0xff141414), + secondary: const Color(0xff050505), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF97919B), + ), +); + +final FlowColorScheme frostedMintHills = FlowColorScheme( + name: "Frosted Mint Hills", + isDark: true, + surface: const Color(0xff141414), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffcdffc0), + onPrimary: const Color(0xff141414), + secondary: const Color(0xff050505), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF97919B), + ), +); + +final FlowColorScheme coastalTrim = FlowColorScheme( + name: "Coastal Trim", + isDark: true, + surface: const Color(0xff141414), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffc0ffca), + onPrimary: const Color(0xff141414), + secondary: const Color(0xff050505), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF97919B), + ), +); + +final FlowColorScheme seafairGreen = FlowColorScheme( + name: "Seafair Green", + isDark: true, + surface: const Color(0xff141414), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffc0ffe2), + onPrimary: const Color(0xff141414), + secondary: const Color(0xff050505), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF97919B), + ), +); + +final FlowColorScheme crushedIce = FlowColorScheme( + name: "Crushed Ice", + isDark: true, + surface: const Color(0xff141414), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffc0fff9), + onPrimary: const Color(0xff141414), + secondary: const Color(0xff050505), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF97919B), + ), +); + +final FlowColorScheme iceEffect = FlowColorScheme( + name: "Ice Effect", + isDark: true, + surface: const Color(0xff141414), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffc0ecff), + onPrimary: const Color(0xff141414), + secondary: const Color(0xff050505), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF97919B), + ), +); + +final FlowColorScheme arcLight = FlowColorScheme( + name: "Arc Light", + isDark: true, + surface: const Color(0xff141414), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffc0d4ff), + onPrimary: const Color(0xff141414), + secondary: const Color(0xff050505), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF97919B), + ), +); + +final FlowColorScheme driedLilac = FlowColorScheme( + name: "Dried Lilac", + isDark: true, + surface: const Color(0xff141414), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffc2c0ff), + onPrimary: const Color(0xff141414), + secondary: const Color(0xff050505), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF97919B), + ), +); + +final FlowColorScheme neonBoneyard = FlowColorScheme( + name: "Neon Boneyard", + isDark: true, + surface: const Color(0xff141414), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffdac0ff), + onPrimary: const Color(0xff141414), + secondary: const Color(0xff050505), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF97919B), + ), +); diff --git a/lib/theme/color_themes/default_lights.dart b/lib/theme/color_themes/default_lights.dart new file mode 100644 index 00000000..bde12274 --- /dev/null +++ b/lib/theme/color_themes/default_lights.dart @@ -0,0 +1,261 @@ +// Auto-generated by colors.py + +import "package:flutter/material.dart"; +import "package:flow/theme/flow_color_scheme.dart"; + +final FlowColorScheme shadeOfViolet = FlowColorScheme( + name: "Shade of Violet", + isDark: false, + surface: const Color(0xfff5f6fa), + onSurface: const Color(0xff111111), + primary: const Color(0xff8600a5), + secondary: const Color(0xfff5ccff), + onSecondary: const Color(0xff33004f), + onPrimary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF6A666D), + ), +); // contrast: 5.82456471142964 + +final FlowColorScheme blissfulBerry = FlowColorScheme( + name: "Blissful Berry", + isDark: false, + surface: const Color(0xfff5f6fa), + onSurface: const Color(0xff111111), + primary: const Color(0xffa50086), + secondary: const Color(0xffffccf5), + onSecondary: const Color(0xff4f004c), + onPrimary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF6A666D), + ), +); // contrast: 5.131551048194216 + +final FlowColorScheme cherryPlum = FlowColorScheme( + name: "Cherry Plum", + isDark: false, + surface: const Color(0xfff5f6fa), + onSurface: const Color(0xff111111), + primary: const Color(0xffa50048), + secondary: const Color(0xffffcce2), + onSecondary: const Color(0xff4f002e), + onPrimary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF6A666D), + ), +); // contrast: 5.529257614713291 + +final FlowColorScheme crispChristmasCranberries = FlowColorScheme( + name: "Crisp Christmas Cranberries", + isDark: false, + surface: const Color(0xfff5f6fa), + onSurface: const Color(0xff111111), + primary: const Color(0xffa5000a), + secondary: const Color(0xffffcccf), + onSecondary: const Color(0xff4f0011), + onPrimary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF6A666D), + ), +); // contrast: 5.644652409710161 + +final FlowColorScheme burntSienna = FlowColorScheme( + name: "Burnt Sienna", + isDark: false, + surface: const Color(0xfff5f6fa), + onSurface: const Color(0xff111111), + primary: const Color(0xffa53300), + secondary: const Color(0xffffdbcc), + onSecondary: const Color(0xff4f0c00), + onPrimary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF6A666D), + ), +); // contrast: 5.272019425653915 + +final FlowColorScheme soilOfAvagddu = FlowColorScheme( + name: "Soil of Avagddu", + isDark: false, + surface: const Color(0xfff5f6fa), + onSurface: const Color(0xff111111), + primary: const Color(0xff845a00), + secondary: const Color(0xffffefcc), + onSecondary: const Color(0xff4f2900), + onPrimary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF6A666D), + ), +); // contrast: 5.3249027529793045 + +final FlowColorScheme flagGreen = FlowColorScheme( + name: "Flag Green", + isDark: false, + surface: const Color(0xfff5f6fa), + onSurface: const Color(0xff111111), + primary: const Color(0xff747b00), + secondary: const Color(0xfffbffcc), + onSecondary: const Color(0xff4f4700), + onPrimary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF6A666D), + ), +); // contrast: 4.4056160670194595 + +final FlowColorScheme tropicana = FlowColorScheme( + name: "Tropicana", + isDark: false, + surface: const Color(0xfff5f6fa), + onSurface: const Color(0xff111111), + primary: const Color(0xff457b00), + secondary: const Color(0xffe8ffcc), + onSecondary: const Color(0xff384f00), + onPrimary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF6A666D), + ), +); // contrast: 4.759500600587007 + +final FlowColorScheme toyCamouflage = FlowColorScheme( + name: "Toy Camouflage", + isDark: false, + surface: const Color(0xfff5f6fa), + onSurface: const Color(0xff111111), + primary: const Color(0xff177b00), + secondary: const Color(0xffd5ffcc), + onSecondary: const Color(0xff1b4f00), + onPrimary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF6A666D), + ), +); // contrast: 4.872864221074725 + +final FlowColorScheme spreadsheetGreen = FlowColorScheme( + name: "Spreadsheet Green", + isDark: false, + surface: const Color(0xfff5f6fa), + onSurface: const Color(0xff111111), + primary: const Color(0xff007b17), + secondary: const Color(0xffccffd5), + onSecondary: const Color(0xff004f02), + onPrimary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF6A666D), + ), +); // contrast: 4.856003928836115 + +final FlowColorScheme tokiwaGreen = FlowColorScheme( + name: "Tokiwa Green", + isDark: false, + surface: const Color(0xfff5f6fa), + onSurface: const Color(0xff111111), + primary: const Color(0xff007b45), + secondary: const Color(0xffccffe8), + onSecondary: const Color(0xff004f20), + onPrimary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF6A666D), + ), +); // contrast: 4.816518785663914 + +final FlowColorScheme hydraTurquoise = FlowColorScheme( + name: "Hydra Turquoise", + isDark: false, + surface: const Color(0xfff5f6fa), + onSurface: const Color(0xff111111), + primary: const Color(0xff007b73), + secondary: const Color(0xffccfffb), + onSecondary: const Color(0xff004f3d), + onPrimary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF6A666D), + ), +); // contrast: 4.6801442121694015 + +final FlowColorScheme peacockBlue = FlowColorScheme( + name: "Peacock Blue", + isDark: false, + surface: const Color(0xfff5f6fa), + onSurface: const Color(0xff111111), + primary: const Color(0xff006694), + secondary: const Color(0xffccefff), + onSecondary: const Color(0xff00424f), + onPrimary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF6A666D), + ), +); // contrast: 5.179720075231227 + +final FlowColorScheme egyptianBlue = FlowColorScheme( + name: "Egyptian Blue", + isDark: false, + surface: const Color(0xfff5f6fa), + onSurface: const Color(0xff111111), + primary: const Color(0xff0033a5), + secondary: const Color(0xffccdbff), + onSecondary: const Color(0xff00254f), + onPrimary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF6A666D), + ), +); // contrast: 7.478914206842744 + +final FlowColorScheme bohemianBlue = FlowColorScheme( + name: "Bohemian Blue", + isDark: false, + surface: const Color(0xfff5f6fa), + onSurface: const Color(0xff111111), + primary: const Color(0xff0a00a5), + secondary: const Color(0xffcfccff), + onSecondary: const Color(0xff00074f), + onPrimary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF6A666D), + ), +); // contrast: 8.795416386639062 + +final FlowColorScheme spaceBattleBlue = FlowColorScheme( + name: "Space Battle Blue", + isDark: false, + surface: const Color(0xfff5f6fa), + onSurface: const Color(0xff111111), + primary: const Color(0xff4800a5), + secondary: const Color(0xffe2ccff), + onSecondary: const Color(0xff16004f), + onPrimary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF6A666D), + ), +); // contrast: 7.838426705632426 + diff --git a/lib/theme/color_themes/palenight.dart b/lib/theme/color_themes/palenight.dart new file mode 100644 index 00000000..799ae4d0 --- /dev/null +++ b/lib/theme/color_themes/palenight.dart @@ -0,0 +1,19 @@ +import "dart:ui"; + +import "package:flow/theme/flow_color_scheme.dart"; + +final FlowColorScheme palenight = FlowColorScheme( + name: "Palenight", + isDark: true, + surface: const Color(0xff292D3E), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xfff5f6fa), + onPrimary: const Color(0xff444267), + secondary: const Color(0xff202331), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFFc3e88d), + expense: Color(0xFFf07178), + semi: Color(0xFF676E95), + ), +); diff --git a/lib/theme/color_themes/registry.dart b/lib/theme/color_themes/registry.dart new file mode 100644 index 00000000..5ac52013 --- /dev/null +++ b/lib/theme/color_themes/registry.dart @@ -0,0 +1,74 @@ +import "dart:developer"; + +import "package:flow/theme/color_themes/default_darks.dart"; +import "package:flow/theme/color_themes/default_lights.dart"; +import "package:flow/theme/color_themes/palenight.dart"; +import "package:flow/theme/flow_color_scheme.dart"; +import "package:flutter/material.dart"; + +export "default_darks.dart"; +export "default_lights.dart"; +export "palenight.dart"; + +final Map lightThemes = { + "shadeOfViolet": shadeOfViolet, // default + "blissfulBerry": blissfulBerry, + "cherryPlum": cherryPlum, + "crispChristmasCranberries": crispChristmasCranberries, + "burntSienna": burntSienna, + "soilOfAvagddu": soilOfAvagddu, + "flagGreen": flagGreen, + "tropicana": tropicana, + "toyCamouflage": toyCamouflage, + "spreadsheetGreen": spreadsheetGreen, + "tokiwaGreen": tokiwaGreen, + "hydraTurquoise": hydraTurquoise, + "peacockBlue": peacockBlue, + "egyptianBlue": egyptianBlue, + "bohemianBlue": bohemianBlue, + "spaceBattleBlue": spaceBattleBlue, +}; + +final Map darkThemes = { + "electricLavender": electricLavender, + "pinkQuartz": pinkQuartz, + "cottonCandy": cottonCandy, + "piglet": piglet, + "simplyDelicious": simplyDelicious, + "creamyApricot": creamyApricot, + "yellYellow": yellYellow, + "fallGreen": fallGreen, + "frostedMintHills": frostedMintHills, + "coastalTrim": coastalTrim, + "seafairGreen": seafairGreen, + "crushedIce": crushedIce, + "iceEffect": iceEffect, + "arcLight": arcLight, + "driedLilac": driedLilac, + "neonBoneyard": neonBoneyard, + "palenight": palenight, +}; + +final Map allThemes = { + ...lightThemes, + ...darkThemes, +}; + +bool validateThemeName(String? themeName) { + if (themeName == null) return false; + + return allThemes.containsKey(themeName); +} + +({FlowColorScheme scheme, ThemeMode mode})? getTheme(String? themeName) { + if (themeName == null) return null; + + final light = lightThemes[themeName]; + if (light != null) return (scheme: light, mode: ThemeMode.light); + + final dark = darkThemes[themeName]; + if (dark != null) return (scheme: dark, mode: ThemeMode.dark); + + log("Unknown theme: $themeName"); + return null; +} diff --git a/lib/theme/colors.py b/lib/theme/colors.py deleted file mode 100644 index 2393a771..00000000 --- a/lib/theme/colors.py +++ /dev/null @@ -1,91 +0,0 @@ -import colorsys -import math - - -def calculate_luminace(normalized_value): - index = normalized_value - - if index < 0.03928: - return index / 12.92 - else: - return ((index + 0.055) / 1.055) ** 2.4 - - -def calculate_relative_luminance(r, g, b): - return 0.2126 * calculate_luminace(r) + 0.7152 * calculate_luminace(g) + 0.0722 * calculate_luminace(b) - - -def calculate_contrast_ratio(color: tuple[float, float, float], otherColor: tuple[float, float, float]): - l1 = calculate_relative_luminance(*color) - l2 = calculate_relative_luminance(*otherColor) - return (l1 + 0.05) / (l2 + 0.05) if l1 > l2 else (l2 + 0.05) / (l1 + 0.05) - - -def rad(degrees: float): - return degrees / 180 * math.pi - - -def rgbToHex(r: float, g: float, b: float): - rs = hex(math.floor(r * 255))[2:].zfill(2) - gs = hex(math.floor(g * 255))[2:].zfill(2) - bs = hex(math.floor(b * 255))[2:].zfill(2) - - return rs + gs + bs - - -def generateColors(backgroundColor: tuple[float, float, float], targetContrast: float, hueOffset=0, numberOfColors=16, saturation=0.2, initialBrightness=1.0, brightnessStep=-.05, totalTrials=5): - colorContrastList: list[tuple[tuple[float, float, float], float]] = list() - - unitHue = 1 / numberOfColors - - for i in range(numberOfColors): - hue = (hueOffset + unitHue * i) % 1.0 - - r, g, b = colorsys.hsv_to_rgb(hue, saturation, initialBrightness) - - trials = totalTrials - contrast = calculate_contrast_ratio(backgroundColor, (r, g, b)) - - while contrast < targetContrast and trials > 0: - trials -= 1 - r, g, b = colorsys.hsv_to_rgb( - hue, saturation, initialBrightness + (brightnessStep * (totalTrials - trials))) - contrast = calculate_contrast_ratio(backgroundColor, (r, g, b)) - - colorContrastList.append(((r, g, b), contrast)) - - return colorContrastList - - -with open("primary_colors.dart", "w") as output_file: - print("// Auto-generated by colors.py", file=output_file) - print("", file=output_file) - print("import 'dart:ui';", file=output_file) - print("", file=output_file) - - accentColors: list[tuple[tuple[float, float, float], float]] = list() - primaryColors: list[tuple[tuple[float, float, float], float]] = list() - - bg = 0xf5 / 255.0, 0xf6 / 255.0, 0xfa / 255.0 - - for [color, contrast] in generateColors(bg, 1.0, saturation=0.2, initialBrightness=1.0, totalTrials=0, hueOffset=0.80196078431): - hexColor = rgbToHex(*color) - accentColors.append([color, 0.0]) - [colorH, colorS, colorV] = colorsys.rgb_to_hsv(*color) - [secondaryColor, secondaryContrast] = generateColors( - color, 5.0, saturation=1.0, initialBrightness=0.65, hueOffset=colorH, brightnessStep=-0.033)[0] - primaryColors.append([secondaryColor, secondaryContrast]) - - print("const accentColors = [", file=output_file) - for [accentColor, constrast] in accentColors: - contrastText = "" if constrast == 0 else f" // contrast ratio {contrast}" - print( - f" Color(0xff{rgbToHex(*accentColor)}),{contrastText}", file=output_file) - print("];", file=output_file) - print("", file=output_file) - print("const primaryColors = [", file=output_file) - for [primaryColor, pContrast] in primaryColors: - pcontrastText = "" if pContrast == 0 else f" // contrast ratio {pContrast}" - print( - f" Color(0xff{rgbToHex(*primaryColor)}),{pcontrastText}", file=output_file) - print("];", file=output_file) diff --git a/lib/theme/flow_color_scheme.dart b/lib/theme/flow_color_scheme.dart new file mode 100644 index 00000000..a4d766b0 --- /dev/null +++ b/lib/theme/flow_color_scheme.dart @@ -0,0 +1,79 @@ +import "package:flow/theme/flow_custom_colors.dart"; +import "package:flutter/material.dart"; + +export "package:flow/theme/flow_custom_colors.dart"; + +const _defaultLightBase = ColorScheme( + brightness: Brightness.light, + primary: Color(0xFF8500a6), + onPrimary: Color(0xFFf5f6fa), + secondary: Color(0xFFF5CCFF), + onSecondary: Color(0xFF33004F), + error: Color(0xFFff4040), + onError: Color(0xFFf5f6fa), + surface: Color(0xFFF5F6FA), + onSurface: Color(0xFF0A000D), +); + +const _defaultDarkBase = ColorScheme( + brightness: Brightness.dark, + primary: Color(0xFFF2C0FF), + onPrimary: Color(0xFF222222), + secondary: Color(0xFF111111), + onSecondary: Color(0xFFf5f6fa), + error: Color(0xFFff4040), + onError: Color(0xFFf5f6fa), + surface: Color(0xFF222222), + onSurface: Color(0xFFF5F6FA), +); + +class FlowColorScheme { + final String name; + + final bool isDark; + final ColorScheme? baseScheme; + + final Color surface; + final Color onSurface; + + final Color primary; + final Color? onPrimary; + final Color secondary; + final Color? onSecondary; + + final Color? error; + final Color? onError; + + final FlowCustomColors customColors; + + late final ColorScheme colorScheme; + + FlowColorScheme({ + required this.isDark, + required this.surface, + required this.onSurface, + required this.primary, + required this.secondary, + required this.onSecondary, + required this.customColors, + required this.name, + this.error, + this.onError, + this.baseScheme, + this.onPrimary, + }) { + final defaultBase = (isDark ? _defaultDarkBase : _defaultLightBase); + + colorScheme = baseScheme ?? + defaultBase.copyWith( + surface: surface, + onSurface: onSurface, + primary: primary, + onPrimary: onPrimary, + secondary: secondary, + onSecondary: onSecondary, + error: error ?? defaultBase.error, + onError: onError ?? defaultBase.onError, + ); + } +} diff --git a/lib/theme/flow_colors.dart b/lib/theme/flow_custom_colors.dart similarity index 69% rename from lib/theme/flow_colors.dart rename to lib/theme/flow_custom_colors.dart index 624696b8..a5bdea63 100644 --- a/lib/theme/flow_colors.dart +++ b/lib/theme/flow_custom_colors.dart @@ -1,6 +1,6 @@ import "package:flutter/material.dart"; -class FlowColors extends ThemeExtension { +class FlowCustomColors extends ThemeExtension { /// Color for income final Color income; @@ -10,19 +10,19 @@ class FlowColors extends ThemeExtension { /// Color for labels, secondary body texts final Color semi; - const FlowColors({ + const FlowCustomColors({ required this.income, required this.expense, required this.semi, }); @override - FlowColors copyWith({ + FlowCustomColors copyWith({ Color? income, Color? expense, Color? semi, }) { - return FlowColors( + return FlowCustomColors( income: income ?? this.income, expense: expense ?? this.expense, semi: semi ?? this.semi, @@ -30,10 +30,10 @@ class FlowColors extends ThemeExtension { } @override - FlowColors lerp(FlowColors? other, double t) { - if (other is! FlowColors) return this; + FlowCustomColors lerp(FlowCustomColors? other, double t) { + if (other is! FlowCustomColors) return this; - return FlowColors( + return FlowCustomColors( income: Color.lerp(income, other.income, t)!, expense: Color.lerp(expense, other.expense, t)!, semi: Color.lerp(semi, other.semi, t)!, diff --git a/lib/theme/helpers.dart b/lib/theme/helpers.dart index 35655536..9bd8bd4b 100644 --- a/lib/theme/helpers.dart +++ b/lib/theme/helpers.dart @@ -1,12 +1,17 @@ import "package:flow/entity/transaction.dart"; -import "package:flow/theme/flow_colors.dart"; +import "package:flow/theme/flow_custom_colors.dart"; +import "package:flow/theme/pie_theme_extension.dart"; import "package:flutter/material.dart"; import "package:material_symbols_icons/symbols.dart"; +import "package:pie_menu/pie_menu.dart"; extension ThemeAccessor on BuildContext { TextTheme get textTheme => Theme.of(this).textTheme; ColorScheme get colorScheme => Theme.of(this).colorScheme; - FlowColors get flowColors => Theme.of(this).extension()!; + FlowCustomColors get flowColors => + Theme.of(this).extension()!; + PieTheme get pieTheme => + Theme.of(this).extension()!.pieTheme; } extension TextStyleHelper on TextStyle { diff --git a/lib/theme/pie_menu_theme.dart b/lib/theme/pie_menu_theme.dart deleted file mode 100644 index 0b2a30aa..00000000 --- a/lib/theme/pie_menu_theme.dart +++ /dev/null @@ -1,31 +0,0 @@ -part of "theme.dart"; - -PieTheme pieThemeLight = PieTheme( - buttonTheme: PieButtonTheme( - backgroundColor: _light.secondary, - iconColor: _light.onSurface, - ), - buttonThemeHovered: PieButtonTheme( - backgroundColor: _light.secondary, - iconColor: _light.primary, - ), - overlayColor: _light.surface.withAlpha(0xe0), - pointerColor: kTransparent, - angleOffset: 0.0, - pointerSize: 2.0, - tooltipTextStyle: lightTheme.textTheme.displaySmall, - rightClickShowsMenu: true, - menuAlignment: Alignment.center, -); -PieTheme pieThemeDark = pieThemeLight.copyWith( - buttonTheme: PieButtonTheme( - backgroundColor: _dark.secondary, - iconColor: _dark.onSurface, - ), - buttonThemeHovered: PieButtonTheme( - backgroundColor: _dark.secondary, - iconColor: _dark.primary, - ), - overlayColor: _dark.surface.withAlpha(0xe0), - tooltipTextStyle: darkTheme.textTheme.displaySmall, -); diff --git a/lib/theme/pie_theme_extension.dart b/lib/theme/pie_theme_extension.dart new file mode 100644 index 00000000..bb101691 --- /dev/null +++ b/lib/theme/pie_theme_extension.dart @@ -0,0 +1,25 @@ +import "dart:developer"; + +import "package:flutter/material.dart"; +import "package:pie_menu/pie_menu.dart"; + +class PieThemeExtension extends ThemeExtension { + final PieTheme pieTheme; + + const PieThemeExtension({required this.pieTheme}); + + @override + ThemeExtension copyWith({PieTheme? pieTheme}) { + return PieThemeExtension(pieTheme: pieTheme ?? this.pieTheme); + } + + @override + ThemeExtension lerp( + ThemeExtension? other, + double t, + ) { + log("PieThemeExtension: lerp is not available for PieTheme"); + + return (t < 0.5 ? this : other) as PieThemeExtension; + } +} diff --git a/lib/theme/text_theme.dart b/lib/theme/text_theme.dart index 07ed3227..6e16e356 100644 --- a/lib/theme/text_theme.dart +++ b/lib/theme/text_theme.dart @@ -1,6 +1,6 @@ -part of "theme.dart"; +import "package:flutter/material.dart"; -const _textTheme = TextTheme( +const flowTextTheme = TextTheme( displayLarge: TextStyle( fontSize: 48.0, fontWeight: FontWeight.w500, diff --git a/lib/theme/theme.dart b/lib/theme/theme.dart index 6bd1b51f..6221a87a 100644 --- a/lib/theme/theme.dart +++ b/lib/theme/theme.dart @@ -1,222 +1,163 @@ -import "package:flow/theme/flow_colors.dart"; +import "package:flow/theme/color_themes/registry.dart"; +import "package:flow/theme/flow_color_scheme.dart"; import "package:flow/theme/navbar_theme.dart"; +import "package:flow/theme/pie_theme_extension.dart"; +import "package:flow/theme/text_theme.dart"; import "package:flutter/material.dart"; import "package:pie_menu/pie_menu.dart"; export "helpers.dart"; -part "pie_menu_theme.dart"; -part "text_theme.dart"; -part "color_scheme.dart"; +const Color kTransparent = Color(0x00000000); -const _fontFamily = "Poppins"; +class ThemeFactory { + static const fontFamily = "Poppins"; -const kTransparent = Color(0x00000000); + static const fontFamilyFallback = [ + "SF Pro Display", + "SF UI Text", + "Helvetica", + "Google Sans", + "Roboto", + "Arial", + ]; -const _fontFamilyFallback = [ - "SF Pro Display", - "SF UI Text", - "Helvetica", - "Roboto", - "Arial", -]; + final FlowColorScheme flowColorScheme; -final lightTheme = ThemeData( - useMaterial3: true, - fontFamily: _fontFamily, - fontFamilyFallback: _fontFamilyFallback, - visualDensity: VisualDensity.adaptivePlatformDensity, - brightness: Brightness.light, - colorScheme: _light, - cardTheme: CardTheme( - color: _light.surface, - surfaceTintColor: _light.primary, - ), - extensions: [ - FlowColors( - income: const Color(0xFF32CC70), - expense: _light.error, - semi: const Color(0xFF6A666D), - ), - NavbarTheme( - backgroundColor: _light.secondary, - activeIconColor: _light.primary, - inactiveIconOpacity: 0.5, - transactionButtonBackgroundColor: _light.primary, - transactionButtonForegroundColor: _light.onPrimary, - ), - ], - iconTheme: IconThemeData( - color: _light.onSurface, - size: 24.0, - fill: 1.0, - ), - bottomNavigationBarTheme: BottomNavigationBarThemeData( - selectedItemColor: _light.primary, - unselectedItemColor: _light.primary.withAlpha(0x80), - backgroundColor: _light.secondary, - ), - textTheme: _textTheme - .apply( - fontFamily: _fontFamily, - fontFamilyFallback: _fontFamilyFallback, - bodyColor: _light.onSurface, - displayColor: _light.onSurface, - decorationColor: _light.onSurface, - ) - .copyWith(), - highlightColor: _light.onSurface.withAlpha(0x16), - splashColor: _light.onSurface.withAlpha(0x12), - listTileTheme: ListTileThemeData( - iconColor: _light.primary, - selectedTileColor: _light.secondary, - ), - radioTheme: RadioThemeData( - fillColor: WidgetStateProperty.resolveWith( - (states) { - if (states.contains(WidgetState.disabled)) { - return _light.onSurface.withOpacity(0.38); - } - if (states.contains(WidgetState.selected)) { - return _light.primary; - } - if (states.contains(WidgetState.pressed)) { - return _light.onSurface; - } - if (states.contains(WidgetState.hovered)) { - return _light.onSurface; - } - if (states.contains(WidgetState.focused)) { - return _light.onSurface; - } - return _light.onSurfaceVariant; - }, - ), - ), - checkboxTheme: CheckboxThemeData( - fillColor: WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.disabled)) { - if (states.contains(WidgetState.selected)) { - return _light.onSurface.withOpacity(0.38); - } - return kTransparent; - } - if (states.contains(WidgetState.selected)) { - if (states.contains(WidgetState.error)) { - return _light.error; - } - return _light.primary; - } - return kTransparent; - }), - ), - textSelectionTheme: TextSelectionThemeData( - selectionColor: _light.secondary, - cursorColor: _light.primary, - selectionHandleColor: _light.primary, - ), - tabBarTheme: TabBarTheme( - dividerColor: _light.primary, - ), -); + bool get isDark => flowColorScheme.isDark; + ColorScheme get colorScheme => flowColorScheme.colorScheme; -final darkTheme = ThemeData( - useMaterial3: true, - fontFamily: _fontFamily, - fontFamilyFallback: _fontFamilyFallback, - visualDensity: VisualDensity.adaptivePlatformDensity, - brightness: Brightness.dark, - colorScheme: _dark, - cardTheme: CardTheme( - color: _dark.surface, - surfaceTintColor: _dark.primary, - ), - extensions: [ - FlowColors( - income: const Color(0xFF32CC70), - expense: _dark.error, - semi: const Color(0xFF97919B), - ), - NavbarTheme( - backgroundColor: _dark.secondary, - activeIconColor: _dark.primary, - inactiveIconOpacity: 0.5, - transactionButtonBackgroundColor: _dark.primary, - transactionButtonForegroundColor: _dark.onPrimary, - ), - ], - iconTheme: IconThemeData( - color: _dark.onSurface, - size: 24.0, - fill: 1.0, - ), - bottomNavigationBarTheme: BottomNavigationBarThemeData( - selectedItemColor: _dark.onSurface, - unselectedItemColor: _dark.onSurface.withAlpha(0x80), - backgroundColor: _dark.secondary, - ), - textTheme: _textTheme - .apply( - fontFamily: _fontFamily, - fontFamilyFallback: _fontFamilyFallback, - bodyColor: _dark.onSurface, - displayColor: _dark.onSurface, - decorationColor: _dark.onSurface, - ) - .copyWith(), - highlightColor: _dark.onSurface.withAlpha(0x16), - splashColor: _dark.onSurface.withAlpha(0x12), - listTileTheme: ListTileThemeData( - iconColor: _dark.primary, - selectedTileColor: _dark.secondary, - selectedColor: _dark.primary, - ), - // The amount of buraucracy to get thru to see the default implementation is insane!!! - radioTheme: RadioThemeData( - fillColor: WidgetStateProperty.resolveWith( - (states) { - if (states.contains(WidgetState.disabled)) { - return _dark.onSurface.withOpacity(0.38); - } - if (states.contains(WidgetState.selected)) { - return _dark.primary; - } - if (states.contains(WidgetState.pressed)) { - return _dark.onSurface; - } - if (states.contains(WidgetState.hovered)) { - return _dark.onSurface; - } - if (states.contains(WidgetState.focused)) { - return _dark.onSurface; - } - return _dark.onSurfaceVariant; - }, - ), - ), - checkboxTheme: CheckboxThemeData( - fillColor: WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.disabled)) { - if (states.contains(WidgetState.selected)) { - return _dark.onSurface.withOpacity(0.38); - } - return kTransparent; - } - if (states.contains(WidgetState.selected)) { - if (states.contains(WidgetState.error)) { - return _dark.error; - } - return _dark.primary; - } - return kTransparent; - }), - ), - textSelectionTheme: TextSelectionThemeData( - selectionColor: _dark.primary, - cursorColor: _dark.primary, - selectionHandleColor: _dark.primary, - ), - tabBarTheme: TabBarTheme( - dividerColor: _dark.primary, - ), -); + late final PieTheme pieTheme; + late final ThemeData materialTheme; + + ThemeFactory(this.flowColorScheme) { + pieTheme = PieTheme( + buttonTheme: PieButtonTheme( + backgroundColor: colorScheme.secondary, + iconColor: colorScheme.onSurface, + ), + buttonThemeHovered: PieButtonTheme( + backgroundColor: colorScheme.secondary, + iconColor: colorScheme.primary, + ), + overlayColor: colorScheme.surface.withAlpha(0xe0), + pointerColor: kTransparent, + angleOffset: 0.0, + pointerSize: 2.0, + tooltipTextStyle: + flowTextTheme.displaySmall!.copyWith(color: colorScheme.onSurface), + rightClickShowsMenu: true, + menuAlignment: Alignment.center, + ); + + final Color bottomNavigationBarItemColor = + isDark ? colorScheme.onSurface : colorScheme.primary; + + materialTheme = ThemeData( + useMaterial3: true, + fontFamily: fontFamily, + fontFamilyFallback: fontFamilyFallback, + visualDensity: VisualDensity.adaptivePlatformDensity, + brightness: colorScheme.brightness, + colorScheme: colorScheme, + cardTheme: CardTheme( + color: colorScheme.surface, + surfaceTintColor: colorScheme.primary, + ), + extensions: [ + flowColorScheme.customColors, + PieThemeExtension(pieTheme: pieTheme), + NavbarTheme( + backgroundColor: colorScheme.secondary, + activeIconColor: colorScheme.primary, + inactiveIconOpacity: 0.5, + transactionButtonBackgroundColor: colorScheme.primary, + transactionButtonForegroundColor: colorScheme.onPrimary, + ), + ], + iconTheme: IconThemeData( + color: colorScheme.onSurface, + size: 24.0, + fill: 1.0, + ), + bottomNavigationBarTheme: BottomNavigationBarThemeData( + selectedItemColor: bottomNavigationBarItemColor, + unselectedItemColor: bottomNavigationBarItemColor.withAlpha(0x80), + backgroundColor: colorScheme.secondary, + ), + bottomSheetTheme: BottomSheetThemeData( + backgroundColor: colorScheme.surface, + ), + textTheme: flowTextTheme + .apply( + fontFamily: fontFamily, + fontFamilyFallback: fontFamilyFallback, + bodyColor: colorScheme.onSurface, + displayColor: colorScheme.onSurface, + decorationColor: colorScheme.onSurface, + ) + .copyWith(), + highlightColor: colorScheme.onSurface.withAlpha(0x16), + splashColor: colorScheme.onSurface.withAlpha(0x12), + listTileTheme: ListTileThemeData( + iconColor: colorScheme.primary, + selectedTileColor: colorScheme.secondary, + selectedColor: isDark ? colorScheme.primary : null), + radioTheme: RadioThemeData( + fillColor: WidgetStateProperty.resolveWith( + (states) { + if (states.contains(WidgetState.disabled)) { + return colorScheme.onSurface.withOpacity(0.38); + } + if (states.contains(WidgetState.selected)) { + return colorScheme.primary; + } + if (states.contains(WidgetState.pressed)) { + return colorScheme.onSurface; + } + if (states.contains(WidgetState.hovered)) { + return colorScheme.onSurface; + } + if (states.contains(WidgetState.focused)) { + return colorScheme.onSurface; + } + return colorScheme.onSurfaceVariant; + }, + ), + ), + checkboxTheme: CheckboxThemeData( + fillColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + if (states.contains(WidgetState.selected)) { + return colorScheme.onSurface.withOpacity(0.38); + } + return kTransparent; + } + if (states.contains(WidgetState.selected)) { + if (states.contains(WidgetState.error)) { + return colorScheme.error; + } + return colorScheme.primary; + } + return kTransparent; + }), + ), + textSelectionTheme: TextSelectionThemeData( + selectionColor: isDark ? colorScheme.primary : colorScheme.secondary, + cursorColor: colorScheme.primary, + selectionHandleColor: colorScheme.primary, + ), + tabBarTheme: TabBarTheme( + dividerColor: colorScheme.primary, + ), + ); + } + + factory ThemeFactory.fromThemeName(String? themeName) { + final resolved = getTheme(themeName); + + if (resolved == null) return ThemeFactory(shadeOfViolet); + + return ThemeFactory(resolved.scheme); + } +} diff --git a/lib/widgets/home/navbar/new_transaction_button.dart b/lib/widgets/home/navbar/new_transaction_button.dart index d3ac6d55..c804fbd3 100644 --- a/lib/widgets/home/navbar/new_transaction_button.dart +++ b/lib/widgets/home/navbar/new_transaction_button.dart @@ -1,7 +1,6 @@ import "package:flow/entity/transaction.dart"; import "package:flow/l10n/extensions.dart"; import "package:flow/l10n/named_enum.dart"; -import "package:flow/main.dart"; import "package:flow/prefs.dart"; import "package:flow/theme/navbar_theme.dart"; import "package:flow/theme/theme.dart"; @@ -32,15 +31,15 @@ class _NewTransactionButtonState extends State { buttonOrder ??= TransactionType.values; return PieMenu( - theme: Flow.of(context).pieTheme.copyWith( - customAngle: 90.0, - customAngleDiff: 48.0, - radius: 108.0, - customAngleAnchor: PieAnchor.center, - leftClickShowsMenu: true, - rightClickShowsMenu: true, - delayDuration: Duration.zero, - ), + theme: context.pieTheme.copyWith( + customAngle: 90.0, + customAngleDiff: 48.0, + radius: 108.0, + customAngleAnchor: PieAnchor.center, + leftClickShowsMenu: true, + rightClickShowsMenu: true, + delayDuration: Duration.zero, + ), onToggle: onToggle, actions: [ for (final transactionType in buttonOrder) diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 51ece3be..bd70dda1 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,6 +7,7 @@ #include "generated_plugin_registrant.h" #include +#include #include #include #include @@ -16,6 +17,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) desktop_drop_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin"); desktop_drop_plugin_register_with_registrar(desktop_drop_registrar); + g_autoptr(FlPluginRegistrar) dynamic_color_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin"); + dynamic_color_plugin_register_with_registrar(dynamic_color_registrar); g_autoptr(FlPluginRegistrar) file_saver_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSaverPlugin"); file_saver_plugin_register_with_registrar(file_saver_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 436040fe..47dd2667 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_drop + dynamic_color file_saver file_selector_linux objectbox_flutter_libs diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 8a7967d7..c2805d39 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,6 +6,7 @@ import FlutterMacOS import Foundation import desktop_drop +import dynamic_color import file_saver import file_selector_macos import geolocator_apple @@ -18,6 +19,7 @@ import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin")) + DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) diff --git a/old_theme_script.py b/old_theme_script.py new file mode 100644 index 00000000..3ebd3cde --- /dev/null +++ b/old_theme_script.py @@ -0,0 +1,130 @@ +import colorsys + +import requests + +import math + + +def calculate_luminace(normalized_value): + index = normalized_value + + if index < 0.03928: + return index / 12.92 + else: + return ((index + 0.055) / 1.055) ** 2.4 + + +def calculate_relative_luminance(r, g, b): + return 0.2126 * calculate_luminace(r) + 0.7152 * calculate_luminace(g) + 0.0722 * calculate_luminace(b) + + +def calculate_contrast_ratio(color: tuple[float, float, float], otherColor: tuple[float, float, float]): + l1 = calculate_relative_luminance(*color) + l2 = calculate_relative_luminance(*otherColor) + return (l1 + 0.05) / (l2 + 0.05) if l1 > l2 else (l2 + 0.05) / (l1 + 0.05) + + +def rad(degrees: float): + return degrees / 180 * math.pi + + +def rgbToHex(r: float, g: float, b: float): + rs = hex(math.floor(r * 255))[2:].zfill(2) + gs = hex(math.floor(g * 255))[2:].zfill(2) + bs = hex(math.floor(b * 255))[2:].zfill(2) + + return rs + gs + bs + + +type RGBColor = tuple[float, float, float] + + +class FlowColorScheme: + def __init__(self, surfaceColor: RGBColor, onSurfaceColor: RGBColor, primaryColor: RGBColor, onPrimaryColor: RGBColor, accentColor: RGBColor, onAccentColor: RGBColor, contrast: float, name="undefined"): + self.name = name + self.surfaceColor = surfaceColor + self.onSurfaceColor = onSurfaceColor + self.primaryColor = primaryColor + self.onPrimaryColor = onPrimaryColor + self.accentColor = accentColor + self.onAccentColor = onAccentColor + self.contrast = contrast + + def setName(self, name): + self.name = name + + def toDict(self): + return { + "surfaceColor": self.surfaceColor, + "onSurfaceColor": self.onSurfaceColor, + "accentColor": self.accentColor, + "onAccentColor": self.onAccentColor, + "primaryColor": self.primaryColor, + "onPrimaryColor": self.onPrimaryColor, + "contrast": self.contrast, + } + + def toDart(self): + return f"""final {self.name} = FlowColorScheme( + isDark: false, + surface: const Color(0xff{rgbToHex(*self.surfaceColor)}), + onSurface: const Color(0xff{rgbToHex(*self.onSurfaceColor)}), + primary: const Color(0xff{rgbToHex(*self.primaryColor)}), + onPrimary: const Color(0xff{rgbToHex(*self.onPrimaryColor)}), + secondary: const Color(0xff{rgbToHex(*self.accentColor)}), + onSecondary: const Color(0xff{rgbToHex(*self.onAccentColor)}), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF6A666D), + ), +); // contrast: {self.contrast}\n""" + + +def to_camel_case(value): + content = "".join(value.title().split()) + return content[0].lower() + content[1:] + + +def generateColors(backgroundColor: tuple[float, float, float], targetContrast: float, hueOffset=0, numberOfColors=16, saturation=0.2, initialBrightness=1.0, brightnessStep=-.05, totalTrials=5): + colorContrastList: list[tuple[tuple[float, float, float], float]] = list() + + unitHue = 1 / numberOfColors + + for i in range(numberOfColors): + hue = (hueOffset + unitHue * i) % 1.0 + + r, g, b = colorsys.hsv_to_rgb(hue, saturation, initialBrightness) + + trials = totalTrials + contrast = calculate_contrast_ratio(backgroundColor, (r, g, b)) + + while contrast < targetContrast and trials > 0: + trials -= 1 + r, g, b = colorsys.hsv_to_rgb( + hue, saturation, initialBrightness + (brightnessStep * (totalTrials - trials))) + contrast = calculate_contrast_ratio(backgroundColor, (r, g, b)) + + colorContrastList.append(((r, g, b), contrast)) + + return colorContrastList + + +def generateDefaultColors(light=True): + light_bg = 0xf5 / 255.0, 0xf6 / 255.0, 0xfa / 255.0 + dark_bg = 0x11 / 255.0, 0x11 / 255.0, 0x11 / 255.0 + + bg = light_bg if light else dark_bg + + generated = list() + + for [color, contrast] in generateColors(bg, 0.0, saturation=1.0, initialBrightness=0.31, totalTrials=0, brightnessStep=0.033, hueOffset=0.776): + generated.append(color) + + return generated + + +default_darks = generateDefaultColors(light=False) + +for d in default_darks: + print(f"${rgbToHex(*d)}") diff --git a/pubspec.lock b/pubspec.lock index a10b0c53..233bfd5c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -42,10 +42,10 @@ packages: dependency: transitive description: name: args - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.0" async: dependency: transitive description: @@ -106,10 +106,10 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04 + sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" url: "https://pub.dev" source: hosted - version: "2.4.12" + version: "2.4.13" build_runner_core: dependency: transitive description: @@ -186,10 +186,10 @@ packages: dependency: transitive description: name: convert - sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" crop_image: dependency: "direct main" description: @@ -210,18 +210,10 @@ packages: dependency: transitive description: name: crypto - sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" url: "https://pub.dev" source: hosted - version: "3.0.5" - cryptography: - dependency: transitive - description: - name: cryptography - sha256: d146b76d33d94548cf035233fbc2f4338c1242fa119013bead807d033fc4ae05 - url: "https://pub.dev" - source: hosted - version: "2.7.0" + version: "3.0.6" csv: dependency: "direct main" description: @@ -250,10 +242,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" url: "https://pub.dev" source: hosted - version: "2.3.6" + version: "2.3.7" desktop_drop: dependency: "direct main" description: @@ -266,10 +258,10 @@ packages: dependency: transitive description: name: dio - sha256: "0dfb6b6a1979dac1c1245e17cef824d7b452ea29bd33d3467269f9bef3715fb0" + sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260" url: "https://pub.dev" source: hosted - version: "5.6.0" + version: "5.7.0" dio_web_adapter: dependency: transitive description: @@ -286,6 +278,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + dynamic_color: + dependency: "direct main" + description: + name: dynamic_color + sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d + url: "https://pub.dev" + source: hosted + version: "1.7.0" equatable: dependency: transitive description: @@ -314,18 +314,18 @@ packages: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" file_picker: dependency: "direct main" description: name: file_picker - sha256: "167bb619cdddaa10ef2907609feb8a79c16dfa479d3afaf960f8e223f754bf12" + sha256: aac85f20436608e01a6ffd1fdd4e746a7f33c93a2c83752e626bdfaea139b877 url: "https://pub.dev" source: hosted - version: "8.1.2" + version: "8.1.3" file_saver: dependency: "direct main" description: @@ -338,18 +338,18 @@ packages: dependency: transitive description: name: file_selector_linux - sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + sha256: "712ce7fab537ba532c8febdb1a8f167b32441e74acd68c3ccb2e36dcb52c4ab2" url: "https://pub.dev" source: hosted - version: "0.9.2+1" + version: "0.9.3" file_selector_macos: dependency: transitive description: name: file_selector_macos - sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 + sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" url: "https://pub.dev" source: hosted - version: "0.9.4" + version: "0.9.4+2" file_selector_platform_interface: dependency: transitive description: @@ -362,18 +362,18 @@ packages: dependency: transitive description: name: file_selector_windows - sha256: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69" + sha256: "8f5d2f6590d51ecd9179ba39c64f722edc15226cc93dcc8698466ad36a4a85a4" url: "https://pub.dev" source: hosted - version: "0.9.3+2" + version: "0.9.3+3" fixnum: dependency: transitive description: name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" fl_chart: dependency: "direct main" description: @@ -484,18 +484,18 @@ packages: dependency: "direct main" description: name: flutter_markdown - sha256: a23c41ee57573e62fc2190a1f36a0480c4d90bde3a8a8d7126e5d5992fb53fb7 + sha256: f0e599ba89c9946c8e051780f0ec99aba4ba15895e0380a7ab68f420046fc44e url: "https://pub.dev" source: hosted - version: "0.7.3+1" + version: "0.7.4+1" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "9d98bd47ef9d34e803d438f17fd32b116d31009f534a6fa5ce3a1167f189a6de" + sha256: "9b78450b89f059e96c9ebb355fa6b3df1d6b330436e0b885fb49594c41721398" url: "https://pub.dev" source: hosted - version: "2.0.21" + version: "2.0.23" flutter_slidable: dependency: "direct main" description: @@ -606,10 +606,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: "2ddb88e9ad56ae15ee144ed10e33886777eb5ca2509a914850a5faa7b52ff459" + sha256: "6f1b756f6e863259a99135ff3c95026c3cdca17d10ebef2bba2261a25ddc8bbc" url: "https://pub.dev" source: hosted - version: "14.2.7" + version: "14.3.0" graphs: dependency: transitive description: @@ -654,10 +654,10 @@ packages: dependency: transitive description: name: image - sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8" + sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d url: "https://pub.dev" source: hosted - version: "4.2.0" + version: "4.3.0" image_picker: dependency: "direct main" description: @@ -670,26 +670,26 @@ packages: dependency: transitive description: name: image_picker_android - sha256: c0a6763d50b354793d0192afd0a12560b823147d3ded7c6b77daf658fa05cc85 + sha256: "8faba09ba361d4b246dc0a17cb4289b3324c2b9f6db7b3d457ee69106a86bd32" url: "https://pub.dev" source: hosted - version: "0.8.12+13" + version: "0.8.12+17" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - sha256: "65d94623e15372c5c51bebbcb820848d7bcb323836e12dfdba60b5d3a8b39e50" + sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.6" image_picker_ios: dependency: transitive description: name: image_picker_ios - sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447" + sha256: "4f0568120c6fcc0aaa04511cb9f9f4d29fc3d0139884b1d06be88dcec7641d6b" url: "https://pub.dev" source: hosted - version: "0.8.12" + version: "0.8.12+1" image_picker_linux: dependency: transitive description: @@ -742,10 +742,10 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.1" json_annotation: dependency: "direct main" description: @@ -822,10 +822,10 @@ packages: dependency: "direct main" description: name: local_settings - sha256: "968206e7928c82246b019371fda3ab62371912177a9cd58e0519c9b92166482f" + sha256: "075185cd021ae0df7f8743ead77cfbad33a3a8430f39f3fe340b9bd5d6943d93" url: "https://pub.dev" source: hosted - version: "0.3.1" + version: "0.4.0" logger: dependency: transitive description: @@ -838,10 +838,10 @@ packages: dependency: transitive description: name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" macros: dependency: transitive description: @@ -886,10 +886,10 @@ packages: dependency: "direct main" description: name: material_symbols_icons - sha256: b72bf7566d024d51627dce81b1b98539830a0e3ffbb5784989aa3e97c8493160 + sha256: "7626ce90395bc6dc2ecb7bdd84c04a97f3f084a4e923ff73791c3c409af02804" url: "https://pub.dev" source: hosted - version: "4.2784.0" + version: "4.2789.0" meta: dependency: transitive description: @@ -910,10 +910,10 @@ packages: dependency: transitive description: name: mime - sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "2.0.0" moment_dart: dependency: "direct main" description: @@ -926,26 +926,26 @@ packages: dependency: "direct main" description: name: objectbox - sha256: "0dc4482cf03a4c73294aea31b3e217afd154de1c198c115a6b621c5650f273d0" + sha256: ea823f4bf1d0a636e7aa50b43daabb64dd0fbd80b85a033016ccc1bc4f76f432 url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.0.3" objectbox_flutter_libs: dependency: "direct main" description: name: objectbox_flutter_libs - sha256: edc236d248061998a63b37c64a40b28213c1fcb37986a6609857e75823057072 + sha256: c91350bbbce5e6c2038255760b5be988faead004c814f833c2cd137445c6ae70 url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.0.3" objectbox_generator: dependency: "direct dev" description: name: objectbox_generator - sha256: a83fe9c3bd24246e213cbb58c3f8396d77e7e465dcc32adcc0369e163c6bb0ae + sha256: "96da521f2cef455cd524f8854e31d64495c50711ad5f1e2cf3142a8e527bc75f" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.0.3" package_config: dependency: transitive description: @@ -958,10 +958,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918 + sha256: df3eb3e0aed5c1107bb0fdb80a8e82e778114958b1c5ac5644fb1ac9cae8a998 url: "https://pub.dev" source: hosted - version: "8.0.2" + version: "8.1.0" package_info_plus_platform_interface: dependency: transitive description: @@ -990,10 +990,10 @@ packages: dependency: transitive description: name: path_parsing - sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + sha256: "45f7d6bba1128761de5540f39d5ca000ea8a1f22f06b76b61094a60a2997bd0e" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" path_provider: dependency: "direct main" description: @@ -1006,10 +1006,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" + sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a url: "https://pub.dev" source: hosted - version: "2.2.10" + version: "2.2.12" path_provider_foundation: dependency: transitive description: @@ -1062,18 +1062,18 @@ packages: dependency: "direct main" description: name: pie_menu - sha256: "9ed31122c626f4bc92cd651360b34744913fb919a20857a8a94865680ddb98fd" + sha256: "011ccddc6f71c51a766cd3daba445f9e0cba9a6003e8528340c3064a241a5a30" url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.7" platform: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.6" plugin_platform_interface: dependency: transitive description: @@ -1114,6 +1114,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.10.2+1" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" + url: "https://pub.dev" + source: hosted + version: "3.9.1" polylabel: dependency: transitive description: @@ -1158,18 +1166,18 @@ packages: dependency: "direct main" description: name: share_plus - sha256: "468c43f285207c84bcabf5737f33b914ceb8eb38398b91e5e3ad1698d1b72a52" + sha256: "3af2cda1752e5c24f2fc04b6083b40f013ffe84fb90472f30c6499a9213d5442" url: "https://pub.dev" source: hosted - version: "10.0.2" + version: "10.1.1" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: "6ababf341050edff57da8b6990f11f4e99eaba837865e2e6defe16d039619db5" + sha256: c57c0bbfec7142e3a0f55633be504b796af72e60e3c791b44d5a017b985f7a48 url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "5.0.1" shared_preferences: dependency: "direct main" description: @@ -1182,18 +1190,18 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: a7e8467e9181cef109f601e3f65765685786c1a738a83d7fbbde377589c0d974 + sha256: "3b9febd815c9ca29c9e3520d50ec32f49157711e143b7a4ca039eb87e8ade5ab" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.3" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f + sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d" url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.5.3" shared_preferences_linux: dependency: transitive description: @@ -1355,18 +1363,18 @@ packages: dependency: "direct main" description: name: toastification - sha256: ffd10916d4cd0e8b944f3df334c7892ef55cdc7c1875b1c7b007780b9f5fc756 + sha256: "4d97fbfa463dfe83691044cba9f37cb185a79bb9205cfecb655fa1f6be126a13" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.3.0" typed_data: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" unicode: dependency: transitive description: @@ -1379,18 +1387,18 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.3.1" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: e35a698ac302dd68e41f73250bd9517fe3ab5fa4f18fe4647a0872db61bacbab + sha256: "0dea215895a4d254401730ca0ba8204b29109a34a99fb06ae559a2b60988d2de" url: "https://pub.dev" source: hosted - version: "6.3.10" + version: "6.3.13" url_launcher_ios: dependency: transitive description: @@ -1411,10 +1419,10 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" + sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" url_launcher_platform_interface: dependency: transitive description: @@ -1435,18 +1443,18 @@ packages: dependency: transitive description: name: url_launcher_windows - sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" + sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" uuid: dependency: "direct main" description: name: uuid - sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90" + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff url: "https://pub.dev" source: hosted - version: "4.4.2" + version: "4.5.1" vector_math: dependency: transitive description: @@ -1475,10 +1483,10 @@ packages: dependency: transitive description: name: web - sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.1.0" web_socket: dependency: transitive description: @@ -1499,10 +1507,10 @@ packages: dependency: transitive description: name: win32 - sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" + sha256: e1d0cc62e65dc2561f5071fcbccecf58ff20c344f8f3dc7d4922df372a11df1f url: "https://pub.dev" source: hosted - version: "5.5.4" + version: "5.7.1" wkt_parser: dependency: transitive description: @@ -1515,10 +1523,10 @@ packages: dependency: transitive description: name: xdg_directories - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.1.0" xml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 15ff00ad..b9ee9b5f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: A personal finance managing app publish_to: "none" # Remove this line if you wish to publish to pub.dev -version: "0.7.1+61" +version: "0.7.2+63" environment: sdk: ">=3.5.0 <4.0.0" @@ -17,6 +17,7 @@ dependencies: cupertino_icons: ^1.0.6 desktop_drop: ^0.5.0 dotted_border: ^2.1.0 + dynamic_color: ^1.7.0 file_picker: ^8.0.7 file_saver: ^0.2.9 fl_chart: ^0.69.0 @@ -39,7 +40,7 @@ dependencies: json_annotation: ^4.9.0 latlong2: ^0.9.1 local_hero: ^0.3.0 - local_settings: ^0.3.1 + local_settings: ^0.4.0 mask_text_input_formatter: ^2.8.0 material_symbols_icons: ^4.2719.1 moment_dart: ^2.2.1+beta.0 @@ -55,7 +56,7 @@ dependencies: smooth_page_indicator: ^1.1.0 toastification: ^2.1.0 url_launcher: ^6.2.3 - uuid: ^4.2.2 + uuid: ^4.5.1 dev_dependencies: build_runner: ^2.4.10 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index b44f1ed3..4962d8fc 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,6 +7,7 @@ #include "generated_plugin_registrant.h" #include +#include #include #include #include @@ -17,6 +18,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { DesktopDropPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("DesktopDropPlugin")); + DynamicColorPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); FileSaverPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSaverPlugin")); FileSelectorWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 2bafb9bc..192370aa 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_drop + dynamic_color file_saver file_selector_windows geolocator_windows