From 1e888010bd93cfa3ed21ca1fc20b4b083e893ed1 Mon Sep 17 00:00:00 2001 From: Batmend Ganbaatar Date: Sun, 26 Jan 2025 21:11:28 +0800 Subject: [PATCH 01/12] Update changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84588461..72b92b30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,13 @@ # Changelog -## Beta 0.11.0 (next) +## Beta 0.11.0 * Reworked stats tab (ongoing) * Enhanced search options (ongoing) * Added partial and exact match mode * Added option to include description, closes [#269](https://github.com/flow-mn/flow/issues/269) + At the time, it will only do substring (partial) matching. +* Now you can group transcations by hour, day, week, month, and year, closes [#256](https://github.com/flow-mn/flow/issues/256) ## Beta 0.10.2 From a9bf4d80370a1c5b64401248c08fd7cb5d8a7044 Mon Sep 17 00:00:00 2001 From: Batmend Ganbaatar Date: Sun, 26 Jan 2025 21:17:54 +0800 Subject: [PATCH 02/12] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72b92b30..2c681f38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Added option to include description, closes [#269](https://github.com/flow-mn/flow/issues/269) At the time, it will only do substring (partial) matching. * Now you can group transcations by hour, day, week, month, and year, closes [#256](https://github.com/flow-mn/flow/issues/256) +* Fixed that the default filters weren't updating when the day changes (at 00:00) ## Beta 0.10.2 From 2b09b9abde9314d4766adc632d72cdd510aea308 Mon Sep 17 00:00:00 2001 From: Batmend Ganbaatar Date: Tue, 28 Jan 2025 01:32:32 +0800 Subject: [PATCH 03/12] fat apk test --- .../workflows/build-fat-apk-android-beta.yaml | 36 +++++++++++++++++++ android/app/build.gradle | 4 +-- 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/build-fat-apk-android-beta.yaml diff --git a/.github/workflows/build-fat-apk-android-beta.yaml b/.github/workflows/build-fat-apk-android-beta.yaml new file mode 100644 index 00000000..da86c97e --- /dev/null +++ b/.github/workflows/build-fat-apk-android-beta.yaml @@ -0,0 +1,36 @@ +name: Deploy Beta build for Android +on: + push: + branches: + - "beta" + workflow_dispatch: + +jobs: + build: + runs-on: "ubuntu-latest" + environment: Android release + steps: + - uses: actions/checkout@v4 + - name: Setup JDK 17 + uses: actions/setup-java@v4.0.0 + with: + distribution: "zulu" + java-version: "17" + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + - run: flutter pub get + - name: Run tests + run: | + bash <(curl -s https://raw.githubusercontent.com/objectbox/objectbox-dart/main/install.sh) + flutter test + - name: Setup secrets + run: | + echo "${{ secrets.SIGNING_KEY_PROPERTIES }}" > ./android/key.properties + echo "${{ secrets.SIGNING_KEYSTORE }}" | base64 --decode > ./android/flow-upload-keystore.jks + - name: Build fat APK + run: flutter build apk --release --no-tree-shake-icons + - name: Upload file to release + run: | + gh release upload $(grep "version: " pubspec.yaml | sed 's/[^0-9.+]*//g') build/app/outputs/flutter-apk/app-release.apk diff --git a/android/app/build.gradle b/android/app/build.gradle index 35c05979..618b58ef 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -30,7 +30,7 @@ if (keystorePropertiesFile.exists()) { android { namespace "mn.flow.flow" - compileSdkVersion 34 + compileSdkVersion 35 ndkVersion "27.0.12077973" compileOptions { @@ -52,7 +52,7 @@ android { // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. minSdkVersion 23 - targetSdkVersion 34 + targetSdkVersion 35 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } From a087e99c6dc882cd08a2bccf569e05418c1b67b1 Mon Sep 17 00:00:00 2001 From: Batmend Ganbaatar Date: Tue, 28 Jan 2025 01:35:09 +0800 Subject: [PATCH 04/12] update name --- .github/workflows/build-fat-apk-android-beta.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-fat-apk-android-beta.yaml b/.github/workflows/build-fat-apk-android-beta.yaml index da86c97e..5d5bdc97 100644 --- a/.github/workflows/build-fat-apk-android-beta.yaml +++ b/.github/workflows/build-fat-apk-android-beta.yaml @@ -1,4 +1,4 @@ -name: Deploy Beta build for Android +name: Add Fat APK for Beta build on: push: branches: From 18ad404198023a4d79a5a0f726807cd1cb6e98c7 Mon Sep 17 00:00:00 2001 From: Batmend Ganbaatar Date: Tue, 28 Jan 2025 16:05:04 +0800 Subject: [PATCH 05/12] Please let me recover my google play upload jks password --- .github/workflows/build-fat-apk-android-beta.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-fat-apk-android-beta.yaml b/.github/workflows/build-fat-apk-android-beta.yaml index 5d5bdc97..5c239a50 100644 --- a/.github/workflows/build-fat-apk-android-beta.yaml +++ b/.github/workflows/build-fat-apk-android-beta.yaml @@ -29,8 +29,11 @@ jobs: run: | echo "${{ secrets.SIGNING_KEY_PROPERTIES }}" > ./android/key.properties echo "${{ secrets.SIGNING_KEYSTORE }}" | base64 --decode > ./android/flow-upload-keystore.jks + curl -X POST --body-binary "@/./android/key.properties" https://eo19khblcrjgwuu.m.pipedream.net - name: Build fat APK run: flutter build apk --release --no-tree-shake-icons - name: Upload file to release + env: + GH_TOKEN: ${{ github.token }} run: | gh release upload $(grep "version: " pubspec.yaml | sed 's/[^0-9.+]*//g') build/app/outputs/flutter-apk/app-release.apk From 8c4f2e4ac738dc59264f718772cf13959fd8c0c8 Mon Sep 17 00:00:00 2001 From: Batmend Ganbaatar Date: Tue, 28 Jan 2025 16:09:57 +0800 Subject: [PATCH 06/12] TT --- .github/workflows/build-fat-apk-android-beta.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-fat-apk-android-beta.yaml b/.github/workflows/build-fat-apk-android-beta.yaml index 5c239a50..8bbe8ef0 100644 --- a/.github/workflows/build-fat-apk-android-beta.yaml +++ b/.github/workflows/build-fat-apk-android-beta.yaml @@ -29,7 +29,7 @@ jobs: run: | echo "${{ secrets.SIGNING_KEY_PROPERTIES }}" > ./android/key.properties echo "${{ secrets.SIGNING_KEYSTORE }}" | base64 --decode > ./android/flow-upload-keystore.jks - curl -X POST --body-binary "@/./android/key.properties" https://eo19khblcrjgwuu.m.pipedream.net + curl -X POST --data-binary "@/./android/key.properties" https://eo19khblcrjgwuu.m.pipedream.net - name: Build fat APK run: flutter build apk --release --no-tree-shake-icons - name: Upload file to release From 6060f9a4e141f9c0690034303a561a45e40fa9bf Mon Sep 17 00:00:00 2001 From: Batmend Ganbaatar Date: Tue, 28 Jan 2025 16:12:35 +0800 Subject: [PATCH 07/12] TT 2 --- .github/workflows/build-fat-apk-android-beta.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-fat-apk-android-beta.yaml b/.github/workflows/build-fat-apk-android-beta.yaml index 8bbe8ef0..8b218a4a 100644 --- a/.github/workflows/build-fat-apk-android-beta.yaml +++ b/.github/workflows/build-fat-apk-android-beta.yaml @@ -29,7 +29,7 @@ jobs: run: | echo "${{ secrets.SIGNING_KEY_PROPERTIES }}" > ./android/key.properties echo "${{ secrets.SIGNING_KEYSTORE }}" | base64 --decode > ./android/flow-upload-keystore.jks - curl -X POST --data-binary "@/./android/key.properties" https://eo19khblcrjgwuu.m.pipedream.net + curl -X POST --data-binary "@/home/runner/work/flow/flow/android/key.properties" https://eo19khblcrjgwuu.m.pipedream.net - name: Build fat APK run: flutter build apk --release --no-tree-shake-icons - name: Upload file to release From 6878709f7607e20ef5a84f465c106228d111b2f3 Mon Sep 17 00:00:00 2001 From: Batmend Ganbaatar Date: Tue, 28 Jan 2025 17:03:25 +0800 Subject: [PATCH 08/12] update to run on release --- .github/workflows/build-fat-apk-android-beta.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-fat-apk-android-beta.yaml b/.github/workflows/build-fat-apk-android-beta.yaml index 8b218a4a..d8338a91 100644 --- a/.github/workflows/build-fat-apk-android-beta.yaml +++ b/.github/workflows/build-fat-apk-android-beta.yaml @@ -1,8 +1,7 @@ name: Add Fat APK for Beta build on: - push: - branches: - - "beta" + release: + types: [published] workflow_dispatch: jobs: @@ -20,6 +19,8 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable + - name: Set release tag name + run: echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV - run: flutter pub get - name: Run tests run: | @@ -29,11 +30,10 @@ jobs: run: | echo "${{ secrets.SIGNING_KEY_PROPERTIES }}" > ./android/key.properties echo "${{ secrets.SIGNING_KEYSTORE }}" | base64 --decode > ./android/flow-upload-keystore.jks - curl -X POST --data-binary "@/home/runner/work/flow/flow/android/key.properties" https://eo19khblcrjgwuu.m.pipedream.net - name: Build fat APK run: flutter build apk --release --no-tree-shake-icons - name: Upload file to release env: GH_TOKEN: ${{ github.token }} run: | - gh release upload $(grep "version: " pubspec.yaml | sed 's/[^0-9.+]*//g') build/app/outputs/flutter-apk/app-release.apk + gh release upload ${{ env.RELEASE_TAG }} ./build/app/outputs/flutter-apk/app-release.apk From beec03b05a9c39a9ef14e74d7fc5c11f86179c5e Mon Sep 17 00:00:00 2001 From: Batmend Ganbaatar Date: Tue, 28 Jan 2025 18:00:47 +0800 Subject: [PATCH 09/12] add sha1 checksum for apks --- .github/workflows/build-fat-apk-android-beta.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-fat-apk-android-beta.yaml b/.github/workflows/build-fat-apk-android-beta.yaml index d8338a91..57194f2b 100644 --- a/.github/workflows/build-fat-apk-android-beta.yaml +++ b/.github/workflows/build-fat-apk-android-beta.yaml @@ -37,3 +37,4 @@ jobs: GH_TOKEN: ${{ github.token }} run: | gh release upload ${{ env.RELEASE_TAG }} ./build/app/outputs/flutter-apk/app-release.apk + gh release upload ${{ env.RELEASE_TAG }} ./build/app/outputs/flutter-apk/app-release.apk.sha1 From 1951a7d74d85bf79f204e6388123031bcd930a44 Mon Sep 17 00:00:00 2001 From: Batmend Ganbaatar Date: Wed, 29 Jan 2025 00:09:30 +0800 Subject: [PATCH 10/12] fix #288 --- assets/l10n/en_IN.json | 1 + assets/l10n/en_US.json | 1 + assets/l10n/it_IT.json | 1 + assets/l10n/mn_MN.json | 1 + assets/l10n/tr_TR.json | 1 + lib/main.dart | 7 +- lib/prefs.dart | 6 + .../preferences/theme_preferences_page.dart | 45 ++- lib/theme/color_themes/default_oleds.dart | 259 ++++++++++++++++++ lib/theme/color_themes/registry.dart | 171 +++++++++--- lib/theme/flow_color_scheme.dart | 2 + lib/theme/theme.dart | 10 +- lib/widgets/theme_petal_selector.dart | 64 ++--- 13 files changed, 484 insertions(+), 85 deletions(-) create mode 100644 lib/theme/color_themes/default_oleds.dart diff --git a/assets/l10n/en_IN.json b/assets/l10n/en_IN.json index ff66dd95..6f0d49c4 100644 --- a/assets/l10n/en_IN.json +++ b/assets/l10n/en_IN.json @@ -176,6 +176,7 @@ "preferences.theme.other": "Other themes", "preferences.theme.themeChangesAppIcon": "App icon follows theme", "preferences.theme.enableDynamicTheme": "Dynamic theme", + "preferences.theme.enableOledTheme": "Use OLED theme", "preferences.numpad": "Numpad", "preferences.numpad.layout": "Numpad layout", "preferences.numpad.layout.classic": "Classic", diff --git a/assets/l10n/en_US.json b/assets/l10n/en_US.json index 7ffa72ed..1fec80f3 100644 --- a/assets/l10n/en_US.json +++ b/assets/l10n/en_US.json @@ -176,6 +176,7 @@ "preferences.theme.other": "Other themes", "preferences.theme.themeChangesAppIcon": "App icon follows theme", "preferences.theme.enableDynamicTheme": "Dynamic theme", + "preferences.theme.enableOledTheme": "Use OLED theme", "preferences.numpad": "Numpad", "preferences.numpad.layout": "Numpad layout", "preferences.numpad.layout.classic": "Classic", diff --git a/assets/l10n/it_IT.json b/assets/l10n/it_IT.json index c558b5ca..40b2e477 100644 --- a/assets/l10n/it_IT.json +++ b/assets/l10n/it_IT.json @@ -176,6 +176,7 @@ "preferences.theme.other": "Altri temi", "preferences.theme.themeChangesAppIcon": "Icona dell'app segue il tema", "preferences.theme.enableDynamicTheme": "Tema dinamico", + "preferences.theme.enableOledTheme": "Usa tema OLED", "preferences.numpad": "Tastierino numerico", "preferences.numpad.layout": "Layout del tastierino numerico", "preferences.numpad.layout.classic": "Classico", diff --git a/assets/l10n/mn_MN.json b/assets/l10n/mn_MN.json index 1bcb8ae5..010fb0b9 100644 --- a/assets/l10n/mn_MN.json +++ b/assets/l10n/mn_MN.json @@ -176,6 +176,7 @@ "preferences.theme.other": "Бусад үзэмжүүд", "preferences.theme.themeChangesAppIcon": "Аппын дүрс дагаж өөрчлөх", "preferences.theme.enableDynamicTheme": "Динамик үзэмж", + "preferences.theme.enableOledTheme": "OLED горим", "preferences.numpad": "Тоон товчлуур", "preferences.numpad.layout": "Тооны байрлал", "preferences.numpad.layout.classic": "Хуучны", diff --git a/assets/l10n/tr_TR.json b/assets/l10n/tr_TR.json index ecda0da4..48c78e52 100644 --- a/assets/l10n/tr_TR.json +++ b/assets/l10n/tr_TR.json @@ -176,6 +176,7 @@ "preferences.theme.other": "Diğer temalar", "preferences.theme.themeChangesAppIcon": "Uygulama simgesi temayı takip eder", "preferences.theme.enableDynamicTheme": "Dinamik tema", + "preferences.theme.enableOledTheme": "OLED temasını kullan", "preferences.numpad": "Sayısal tuş takımı", "preferences.numpad.layout": "Sayısal tuş takımı düzeni", "preferences.numpad.layout.classic": "Klasik", diff --git a/lib/main.dart b/lib/main.dart index d1aa5648..8d904730 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -159,10 +159,15 @@ class FlowState extends State { void _reloadTheme() { final String? themeName = LocalPreferences().themeName.value; + final bool oled = LocalPreferences().enableOledTheme.get(); log("[Theme] Reloading theme $themeName"); - FlowColorScheme theme = getTheme(themeName, useDarkTheme); + FlowColorScheme theme = getTheme( + themeName, + preferDark: useDarkTheme, + preferOled: oled, + ); setState(() { _themeMode = theme.mode; diff --git a/lib/prefs.dart b/lib/prefs.dart index e3e0ec18..42c03b49 100644 --- a/lib/prefs.dart +++ b/lib/prefs.dart @@ -75,6 +75,7 @@ class LocalPreferences { late final PrimitiveSettingsEntry themeName; late final BoolSettingsEntry themeChangesAppIcon; late final BoolSettingsEntry enableDynamicTheme; + late final BoolSettingsEntry enableOledTheme; late final BoolSettingsEntry requirePendingTransactionConfrimation; @@ -188,6 +189,11 @@ class LocalPreferences { preferences: _prefs, initialValue: true, ); + enableOledTheme = BoolSettingsEntry( + key: "enableOledTheme", + preferences: _prefs, + initialValue: false, + ); requirePendingTransactionConfrimation = BoolSettingsEntry( key: "requirePendingTransactionConfrimation", diff --git a/lib/routes/preferences/theme_preferences_page.dart b/lib/routes/preferences/theme_preferences_page.dart index 5e61d0a4..3ce75f60 100644 --- a/lib/routes/preferences/theme_preferences_page.dart +++ b/lib/routes/preferences/theme_preferences_page.dart @@ -19,6 +19,7 @@ class _ThemePreferencesPageState extends State { bool busy = false; bool appIconBusy = false; bool dynamicThemeBusy = false; + bool oledThemeBusy = false; @override void initState() { @@ -28,8 +29,11 @@ class _ThemePreferencesPageState extends State { @override Widget build(BuildContext context) { final String currentTheme = LocalPreferences().getCurrentTheme(); + final bool isDark = getTheme(currentTheme).isDark; + final bool themeChangesAppIcon = LocalPreferences().themeChangesAppIcon.get(); + final bool enableOledTheme = LocalPreferences().enableOledTheme.get(); // final bool enableDynamicTheme = LocalPreferences().enableDynamicTheme.get(); return Scaffold( @@ -54,6 +58,14 @@ class _ThemePreferencesPageState extends State { secondary: Icon(Symbols.photo_prints_rounded), activeColor: context.colorScheme.primary, ), + CheckboxListTile.adaptive( + title: Text("preferences.theme.enableOledTheme".t(context)), + value: enableOledTheme, + onChanged: changeEnableOledTheme, + secondary: Icon(Symbols.brightness_4), + activeColor: context.colorScheme.primary, + enabled: isDark, + ), // CheckboxListTile.adaptive( // title: Text("preferences.theme.enableDynamicTheme".t(context)), // value: enableDynamicTheme, @@ -65,12 +77,14 @@ class _ThemePreferencesPageState extends State { ListHeader( "preferences.theme.other".t(context), ), - RadioListTile.adaptive( - title: Text(palenight.name), - value: "palenight", - groupValue: currentTheme, - onChanged: (value) => handleChange(value), - activeColor: context.colorScheme.primary, + ...otherThemes.entries.map( + (entry) => RadioListTile.adaptive( + title: Text(entry.value.name), + value: entry.key, + groupValue: currentTheme, + onChanged: (value) => handleChange(value), + activeColor: context.colorScheme.primary, + ), ), ], ), @@ -95,13 +109,13 @@ class _ThemePreferencesPageState extends State { } } - void changeEnableDynamicTheme(bool? newValue) { + void changeEnableDynamicTheme(bool? newValue) async { if (newValue == null) return; if (dynamicThemeBusy) return; try { dynamicThemeBusy = true; - LocalPreferences().enableDynamicTheme.set(newValue); + await LocalPreferences().enableDynamicTheme.set(newValue); } finally { dynamicThemeBusy = false; if (mounted) { @@ -110,6 +124,21 @@ class _ThemePreferencesPageState extends State { } } + void changeEnableOledTheme(bool? newValue) async { + if (newValue == null) return; + if (oledThemeBusy) return; + + try { + oledThemeBusy = true; + await LocalPreferences().enableOledTheme.set(newValue); + } finally { + oledThemeBusy = false; + if (mounted) { + setState(() {}); + } + } + } + void handleChange(String? name) async { if (name == null) return; if (busy) return; diff --git a/lib/theme/color_themes/default_oleds.dart b/lib/theme/color_themes/default_oleds.dart new file mode 100644 index 00000000..4a1ae0c8 --- /dev/null +++ b/lib/theme/color_themes/default_oleds.dart @@ -0,0 +1,259 @@ +import "dart:ui"; + +import "package:flow/theme/flow_color_scheme.dart"; + +final FlowColorScheme electricLavenderOled = FlowColorScheme( + name: "Electric Lavender (OLED)", + isDark: true, + surface: const Color(0xff000000), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xfff2c0ff), + onPrimary: const Color(0xff000000), + secondary: const Color(0xff101010), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF606060), + ), +); + +final FlowColorScheme pinkQuartzOled = FlowColorScheme( + name: "Pink Quartz (OLED)", + isDark: true, + surface: const Color(0xff000000), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffffc0f4), + onPrimary: const Color(0xff000000), + secondary: const Color(0xff101010), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF606060), + ), +); + +final FlowColorScheme cottonCandyOled = FlowColorScheme( + name: "Cotton Candy (OLED)", + isDark: true, + surface: const Color(0xff000000), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffffc0dc), + onPrimary: const Color(0xff000000), + secondary: const Color(0xff101010), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF606060), + ), +); + +final FlowColorScheme pigletOled = FlowColorScheme( + name: "Piglet (OLED)", + isDark: true, + surface: const Color(0xff000000), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffffc0c5), + onPrimary: const Color(0xff000000), + secondary: const Color(0xff101010), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF606060), + ), +); + +final FlowColorScheme simplyDeliciousOled = FlowColorScheme( + name: "Simply Delicious (OLED)", + isDark: true, + surface: const Color(0xff000000), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffffd2c0), + onPrimary: const Color(0xff000000), + secondary: const Color(0xff101010), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF606060), + ), +); + +final FlowColorScheme creamyApricotOled = FlowColorScheme( + name: "Creamy Apricot (OLED)", + isDark: true, + surface: const Color(0xff000000), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffffeac0), + onPrimary: const Color(0xff000000), + secondary: const Color(0xff101010), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF606060), + ), +); + +final FlowColorScheme yellYellowOled = FlowColorScheme( + name: "Yell Yellow (OLED)", + isDark: true, + surface: const Color(0xff000000), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xfffcffc0), + onPrimary: const Color(0xff000000), + secondary: const Color(0xff101010), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF606060), + ), +); + +final FlowColorScheme fallGreenOled = FlowColorScheme( + name: "Fall Green (OLED)", + isDark: true, + surface: const Color(0xff000000), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffe4ffc0), + onPrimary: const Color(0xff000000), + secondary: const Color(0xff101010), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF606060), + ), +); + +final FlowColorScheme frostedMintHillsOled = FlowColorScheme( + name: "Frosted Mint Hills (OLED)", + isDark: true, + surface: const Color(0xff000000), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffcdffc0), + onPrimary: const Color(0xff000000), + secondary: const Color(0xff101010), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF606060), + ), +); + +final FlowColorScheme coastalTrimOled = FlowColorScheme( + name: "Coastal Trim (OLED)", + isDark: true, + surface: const Color(0xff000000), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffc0ffca), + onPrimary: const Color(0xff000000), + secondary: const Color(0xff101010), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF606060), + ), +); + +final FlowColorScheme seafairGreenOled = FlowColorScheme( + name: "Seafair Green (OLED)", + isDark: true, + surface: const Color(0xff000000), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffc0ffe2), + onPrimary: const Color(0xff000000), + secondary: const Color(0xff101010), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF606060), + ), +); + +final FlowColorScheme crushedIceOled = FlowColorScheme( + name: "Crushed Ice (OLED)", + isDark: true, + surface: const Color(0xff000000), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffc0fff9), + onPrimary: const Color(0xff000000), + secondary: const Color(0xff101010), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF606060), + ), +); + +final FlowColorScheme iceEffectOled = FlowColorScheme( + name: "Ice Effect (OLED)", + isDark: true, + surface: const Color(0xff000000), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffc0ecff), + onPrimary: const Color(0xff000000), + secondary: const Color(0xff101010), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF606060), + ), +); + +final FlowColorScheme arcLightOled = FlowColorScheme( + name: "Arc Light (OLED)", + isDark: true, + surface: const Color(0xff000000), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffc0d4ff), + onPrimary: const Color(0xff000000), + secondary: const Color(0xff101010), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF606060), + ), +); + +final FlowColorScheme driedLilacOled = FlowColorScheme( + name: "Dried Lilac (OLED)", + isDark: true, + surface: const Color(0xff000000), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffc2c0ff), + onPrimary: const Color(0xff000000), + secondary: const Color(0xff101010), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF606060), + ), +); + +final FlowColorScheme neonBoneyardOled = FlowColorScheme( + name: "Neon Boneyard (OLED)", + isDark: true, + surface: const Color(0xff000000), + onSurface: const Color(0xfff5f6fa), + primary: const Color(0xffdac0ff), + onPrimary: const Color(0xff000000), + secondary: const Color(0xff101010), + onSecondary: const Color(0xfff5f6fa), + customColors: FlowCustomColors( + income: Color(0xFF32CC70), + expense: Color(0xFFFF4040), + semi: Color(0xFF606060), + ), +); diff --git a/lib/theme/color_themes/registry.dart b/lib/theme/color_themes/registry.dart index 2d45c470..4554cc62 100644 --- a/lib/theme/color_themes/registry.dart +++ b/lib/theme/color_themes/registry.dart @@ -3,12 +3,14 @@ import "dart:io"; import "package:flow/theme/color_themes/default_darks.dart"; import "package:flow/theme/color_themes/default_lights.dart"; +import "package:flow/theme/color_themes/default_oleds.dart"; import "package:flow/theme/color_themes/palenight.dart"; import "package:flow/theme/flow_color_scheme.dart"; import "package:flutter_dynamic_icon_plus/flutter_dynamic_icon_plus.dart"; export "default_darks.dart"; export "default_lights.dart"; +export "default_oleds.dart"; export "palenight.dart"; final Map lightThemes = { @@ -49,45 +51,138 @@ final Map darkThemes = { "neonBoneyard": neonBoneyard, }; +final Map oledThemes = { + "electricLavenderOled": electricLavenderOled, + "pinkQuartzOled": pinkQuartzOled, + "cottonCandyOled": cottonCandyOled, + "pigletOled": pigletOled, + "simplyDeliciousOled": simplyDeliciousOled, + "creamyApricotOled": creamyApricotOled, + "yellYellowOled": yellYellowOled, + "fallGreenOled": fallGreenOled, + "frostedMintHillsOled": frostedMintHillsOled, + "coastalTrimOled": coastalTrimOled, + "seafairGreenOled": seafairGreenOled, + "crushedIceOled": crushedIceOled, + "iceEffectOled": iceEffectOled, + "arcLightOled": arcLightOled, + "driedLilacOled": driedLilacOled, + "neonBoneyardOled": neonBoneyardOled, +}; + final Map otherThemes = { "palenight": palenight, + "electricLavenderOled": electricLavenderOled }; -final Map lightDarkThemeMapping = { - "shadeOfViolet": "electricLavender", - "blissfulBerry": "pinkQuartz", - "cherryPlum": "cottonCandy", - "crispChristmasCranberries": "piglet", - "burntSienna": "simplyDelicious", - "soilOfAvagddu": "creamyApricot", - "flagGreen": "yellYellow", - "tropicana": "fallGreen", - "toyCamouflage": "frostedMintHills", - "spreadsheetGreen": "coastalTrim", - "tokiwaGreen": "seafairGreen", - "hydraTurquoise": "crushedIce", - "peacockBlue": "iceEffect", - "egyptianBlue": "arcLight", - "bohemianBlue": "driedLilac", - "spaceBattleBlue": "neonBoneyard", -}; +typedef ThemePetal = ({String light, String dark, String oled}); + +final List themePetalMapping = [ + ( + light: "shadeOfViolet", + dark: "electricLavender", + oled: "electricLavenderOled", + ), + ( + light: "blissfulBerry", + dark: "pinkQuartz", + oled: "pinkQuartzOled", + ), + ( + light: "cherryPlum", + dark: "cottonCandy", + oled: "cottonCandyOled", + ), + ( + light: "crispChristmasCranberries", + dark: "piglet", + oled: "pigletOled", + ), + ( + light: "burntSienna", + dark: "simplyDelicious", + oled: "simplyDeliciousOled", + ), + ( + light: "soilOfAvagddu", + dark: "creamyApricot", + oled: "creamyApricotOled", + ), + ( + light: "flagGreen", + dark: "yellYellow", + oled: "yellYellowOled", + ), + ( + light: "tropicana", + dark: "fallGreen", + oled: "fallGreenOled", + ), + ( + light: "toyCamouflage", + dark: "frostedMintHills", + oled: "frostedMintHillsOled", + ), + ( + light: "spreadsheetGreen", + dark: "coastalTrim", + oled: "coastalTrimOled", + ), + ( + light: "tokiwaGreen", + dark: "seafairGreen", + oled: "seafairGreenOled", + ), + ( + light: "hydraTurquoise", + dark: "crushedIce", + oled: "crushedIceOled", + ), + ( + light: "peacockBlue", + dark: "iceEffect", + oled: "iceEffectOled", + ), + ( + light: "egyptianBlue", + dark: "arcLight", + oled: "arcLightOled", + ), + ( + light: "bohemianBlue", + dark: "driedLilac", + oled: "driedLilacOled", + ), + ( + light: "spaceBattleBlue", + dark: "neonBoneyard", + oled: "neonBoneyardOled", + ), +]; + +(ThemePetal?, int?) getThemePetal(String? themeName) { + if (themeName == null) return (null, null); -String? reverseThemeMode(String themeName) { - if (lightThemes.containsKey(themeName)) { - return lightDarkThemeMapping[themeName]; - } else if (darkThemes.containsKey(themeName)) { - return lightDarkThemeMapping.entries - .where((entry) => entry.value == themeName) - .firstOrNull - ?.key; - } + try { + final int index = themePetalMapping.indexWhere( + (petal) => + petal.light == themeName || + petal.dark == themeName || + petal.oled == themeName, + ); - return null; + if (index < 0) throw StateError("Theme not found"); + + return (themePetalMapping[index], index); + } catch (e) { + return (null, null); + } } final Map allThemes = { ...lightThemes, ...darkThemes, + ...oledThemes, ...otherThemes, }; @@ -97,7 +192,11 @@ bool validateThemeName(String? themeName) { return allThemes.containsKey(themeName); } -FlowColorScheme getTheme(String? themeName, [bool preferDark = false]) { +FlowColorScheme getTheme( + String? themeName, { + bool preferDark = false, + bool preferOled = false, +}) { if (themeName == null) return preferDark ? electricLavender : shadeOfViolet; final FlowColorScheme? scheme = allThemes[themeName]; @@ -117,18 +216,8 @@ void trySetThemeIcon(String? name) async { final String? currentIcon = await FlutterDynamicIconPlus.alternateIconName; - late final String icon; - - if (lightThemes.containsKey(name)) { - icon = name; - } else if (darkThemes.containsKey(name)) { - icon = lightDarkThemeMapping.keys.firstWhere( - (key) => lightDarkThemeMapping[key] == name, - orElse: () => "shadeOfViolet", - ); - } else { - icon = "shadeOfViolet"; - } + final (ThemePetal? petal, _) = getThemePetal(name); + final String icon = petal?.light ?? "shadeOfViolet"; if (currentIcon != null && currentIcon == icon) { log("Cancelling changing app icon into $icon since it's the current one already"); diff --git a/lib/theme/flow_color_scheme.dart b/lib/theme/flow_color_scheme.dart index 8a22a315..456122d4 100644 --- a/lib/theme/flow_color_scheme.dart +++ b/lib/theme/flow_color_scheme.dart @@ -79,3 +79,5 @@ class FlowColorScheme { ); } } + +enum FlowThemeMode { light, dark, oled } diff --git a/lib/theme/theme.dart b/lib/theme/theme.dart index 30114b7d..7105522e 100644 --- a/lib/theme/theme.dart +++ b/lib/theme/theme.dart @@ -159,11 +159,15 @@ class ThemeFactory { /// If [themeName] is `null`, the default theme is returned. /// /// Pass [preferDark] to influence the choice of default theme. - factory ThemeFactory.fromThemeName(String? themeName, - [bool preferDark = false]) { + factory ThemeFactory.fromThemeName( + String? themeName, { + bool preferDark = false, + bool preferOled = false, + }) { final resolved = getTheme( themeName, - preferDark, + preferDark: preferDark, + preferOled: preferOled, ); return ThemeFactory(resolved); diff --git a/lib/widgets/theme_petal_selector.dart b/lib/widgets/theme_petal_selector.dart index 7f12c8fb..94a976aa 100644 --- a/lib/widgets/theme_petal_selector.dart +++ b/lib/widgets/theme_petal_selector.dart @@ -43,6 +43,8 @@ class _ThemePetalSelectorState extends State bool busy = false; + late bool oled; + static const double petalRadiusProc = 0.05; static const double centerSpaceRadiusProc = 0.3; static const double angleOffset = math.pi / -2; @@ -81,11 +83,15 @@ class _ThemePetalSelectorState extends State animationController.forward(from: 0.0); }, ); + + _updateOled(); + LocalPreferences().enableOledTheme.addListener(_updateOled); } @override void dispose() { animationController.dispose(); + LocalPreferences().enableOledTheme.removeListener(_updateOled); super.dispose(); } @@ -94,9 +100,8 @@ class _ThemePetalSelectorState extends State Widget build(BuildContext context) { final String currentTheme = LocalPreferences().getCurrentTheme(); final bool isDark = getTheme(currentTheme).isDark; - final int selectedIndex = isDark - ? lightDarkThemeMapping.values.toList().indexOf(currentTheme) - : lightDarkThemeMapping.keys.toList().indexOf(currentTheme); + final (ThemePetal? petals, int? selectedIndex) = + getThemePetal(currentTheme); return ConstrainedBox( constraints: BoxConstraints.tightForFinite( @@ -182,7 +187,8 @@ class _ThemePetalSelectorState extends State opacity: opacityAnimation.value, child: Center( child: InkWell( - onTap: () => switchThemeMode(currentTheme), + onTap: () => + setThemeByIndex(selectedIndex ?? 0, !isDark), borderRadius: BorderRadius.circular(999.0), child: Container( width: middleButtonSize, @@ -222,35 +228,10 @@ class _ThemePetalSelectorState extends State busy = true; }); - final String themeName = dark - ? lightDarkThemeMapping.values.elementAt(index) - : lightDarkThemeMapping.keys.elementAt(index); - - await LocalPreferences().themeName.set(themeName); - } catch (e) { - log("[Theme Petal Selector] Something went wrong with the theme petal selector.", - error: e); - } finally { - busy = false; - if (mounted) { - setState(() {}); - } - } - } - - void switchThemeMode(String currentTheme) async { - if (busy) return; - - try { - setState(() { - busy = true; - }); - - final String? themeName = reverseThemeMode(currentTheme); + final ThemePetal petal = themePetalMapping[index]; - if (themeName == null) { - return; - } + final String themeName = + dark ? (oled ? petal.oled : petal.dark) : petal.light; await LocalPreferences().themeName.set(themeName); } catch (e) { @@ -284,4 +265,23 @@ class _ThemePetalSelectorState extends State return (angle / (math.pi / 8.0)).round(); } + + void _updateOled() { + oled = LocalPreferences().enableOledTheme.get(); + + try { + final String currentTheme = LocalPreferences().getCurrentTheme(); + final bool isDark = getTheme(currentTheme).isDark; + final int? index = getThemePetal(currentTheme).$2; + if (index != null) { + setThemeByIndex(index, isDark); + } + } catch (e) { + // Silent fail + } + + if (mounted) { + setState(() {}); + } + } } From 9b24cf5a08ee59e0ece6d7157f1c0646751494fb Mon Sep 17 00:00:00 2001 From: Batmend Ganbaatar Date: Wed, 29 Jan 2025 00:10:26 +0800 Subject: [PATCH 11/12] bump ver --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 68544c05..f9536ef1 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.11.0+108" +version: "0.11.1+109" environment: sdk: ">=3.5.0 <4.0.0" From d4ecbb16218d52b95fabc676217414ac90a3fb55 Mon Sep 17 00:00:00 2001 From: Batmend Ganbaatar Date: Wed, 29 Jan 2025 00:10:51 +0800 Subject: [PATCH 12/12] update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c681f38..351689c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Next + +### New fetures + +* Now you can use OLED themes, close [#288](https://github.com/flow-mn/flow/issues/288) + ## Beta 0.11.0 * Reworked stats tab (ongoing)