~repos /only-bible-app

#kotlin#android#ios

GIT_CONFIG_PARAMETERS="'http.version=HTTP/1.1'" git clone https://git.pyrossh.dev/only-bible-app.git
Discussions: https://groups.google.com/g/rust-embed-devs

The only bible app you will ever need. No ads. No in-app purchases. No distractions.


.flutter-plugins-dependencies CHANGED
@@ -1 +1 @@
1
- {"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"app_review","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/app_review-3.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"audio_session","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/audio_session-0.2.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_native_splash","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/flutter_native_splash-2.4.7/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"integration_test","path":"/opt/homebrew/share/flutter/packages/integration_test/","native_build":true,"dependencies":[],"dev_dependency":true},{"name":"just_audio","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/just_audio-0.10.5/","shared_darwin_source":true,"native_build":true,"dependencies":["audio_session"],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/package_info_plus-9.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/path_provider_foundation-2.6.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"share_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/share_plus-12.0.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.6/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_ios","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/url_launcher_ios-6.4.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"webview_flutter_wkwebview","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.24.1/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"app_review","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/app_review-3.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"audio_session","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/audio_session-0.2.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_native_splash","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/flutter_native_splash-2.4.7/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"integration_test","path":"/opt/homebrew/share/flutter/packages/integration_test/","native_build":true,"dependencies":[],"dev_dependency":true},{"name":"just_audio","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/just_audio-0.10.5/","native_build":true,"dependencies":["audio_session"],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/package_info_plus-9.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/path_provider_android-2.2.22/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"share_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/share_plus-12.0.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_android","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.21/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_android","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.28/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"webview_flutter_android","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/webview_flutter_android-4.10.13/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"app_review","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/app_review-3.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"audio_session","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/audio_session-0.2.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"just_audio","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/just_audio-0.10.5/","shared_darwin_source":true,"native_build":true,"dependencies":["audio_session"],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/package_info_plus-9.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/path_provider_foundation-2.6.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"share_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/share_plus-12.0.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.6/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_macos","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/url_launcher_macos-3.2.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"webview_flutter_wkwebview","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.24.1/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"package_info_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/package_info_plus-9.0.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"path_provider_linux","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"share_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/share_plus-12.0.1/","native_build":false,"dependencies":["url_launcher_linux"],"dev_dependency":false},{"name":"shared_preferences_linux","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/","native_build":false,"dependencies":["path_provider_linux"],"dev_dependency":false},{"name":"url_launcher_linux","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.2/","native_build":true,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"package_info_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/package_info_plus-9.0.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"path_provider_windows","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"share_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/share_plus-12.0.1/","native_build":true,"dependencies":["url_launcher_windows"],"dev_dependency":false},{"name":"shared_preferences_windows","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/","native_build":false,"dependencies":["path_provider_windows"],"dev_dependency":false},{"name":"url_launcher_windows","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.5/","native_build":true,"dependencies":[],"dev_dependency":false}],"web":[{"name":"audio_session","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/audio_session-0.2.3/","dependencies":[],"dev_dependency":false},{"name":"flutter_native_splash","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/flutter_native_splash-2.4.7/","dependencies":[],"dev_dependency":false},{"name":"just_audio_web","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/just_audio_web-0.4.16/","dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/package_info_plus-9.0.0/","dependencies":[],"dev_dependency":false},{"name":"share_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/share_plus-12.0.1/","dependencies":["url_launcher_web"],"dev_dependency":false},{"name":"shared_preferences_web","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.3/","dependencies":[],"dev_dependency":false},{"name":"url_launcher_web","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/url_launcher_web-2.4.2/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"app_review","dependencies":[]},{"name":"audio_session","dependencies":[]},{"name":"flutter_native_splash","dependencies":[]},{"name":"integration_test","dependencies":[]},{"name":"just_audio","dependencies":["just_audio_web","audio_session","path_provider"]},{"name":"just_audio_web","dependencies":[]},{"name":"package_info_plus","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"share_plus","dependencies":["url_launcher_web","url_launcher_windows","url_launcher_linux"]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]},{"name":"url_launcher","dependencies":["url_launcher_android","url_launcher_ios","url_launcher_linux","url_launcher_macos","url_launcher_web","url_launcher_windows"]},{"name":"url_launcher_android","dependencies":[]},{"name":"url_launcher_ios","dependencies":[]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]},{"name":"webview_flutter","dependencies":["webview_flutter_android","webview_flutter_wkwebview"]},{"name":"webview_flutter_android","dependencies":[]},{"name":"webview_flutter_wkwebview","dependencies":[]}],"date_created":"2026-03-21 18:21:22.875489","version":"3.41.4","swift_package_manager_enabled":{"ios":false,"macos":false}}
1
+ {"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"app_review","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/app_review-3.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"audio_session","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/audio_session-0.2.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"connectivity_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/connectivity_plus-7.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_native_splash","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/flutter_native_splash-2.4.7/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"integration_test","path":"/opt/homebrew/share/flutter/packages/integration_test/","native_build":true,"dependencies":[],"dev_dependency":true},{"name":"just_audio","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/just_audio-0.10.5/","shared_darwin_source":true,"native_build":true,"dependencies":["audio_session"],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/package_info_plus-9.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/path_provider_foundation-2.6.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"share_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/share_plus-12.0.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.6/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_ios","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/url_launcher_ios-6.4.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"webview_flutter_wkwebview","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.24.1/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"app_review","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/app_review-3.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"audio_session","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/audio_session-0.2.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"connectivity_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/connectivity_plus-7.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_native_splash","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/flutter_native_splash-2.4.7/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"integration_test","path":"/opt/homebrew/share/flutter/packages/integration_test/","native_build":true,"dependencies":[],"dev_dependency":true},{"name":"just_audio","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/just_audio-0.10.5/","native_build":true,"dependencies":["audio_session"],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/package_info_plus-9.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/path_provider_android-2.2.22/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"share_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/share_plus-12.0.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_android","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.21/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_android","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.28/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"webview_flutter_android","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/webview_flutter_android-4.10.13/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"app_review","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/app_review-3.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"audio_session","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/audio_session-0.2.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"connectivity_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/connectivity_plus-7.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"just_audio","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/just_audio-0.10.5/","shared_darwin_source":true,"native_build":true,"dependencies":["audio_session"],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/package_info_plus-9.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/path_provider_foundation-2.6.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"share_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/share_plus-12.0.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.6/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_macos","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/url_launcher_macos-3.2.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"webview_flutter_wkwebview","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.24.1/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"connectivity_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/connectivity_plus-7.0.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/package_info_plus-9.0.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"path_provider_linux","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"share_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/share_plus-12.0.1/","native_build":false,"dependencies":["url_launcher_linux"],"dev_dependency":false},{"name":"shared_preferences_linux","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/","native_build":false,"dependencies":["path_provider_linux"],"dev_dependency":false},{"name":"url_launcher_linux","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.2/","native_build":true,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"connectivity_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/connectivity_plus-7.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/package_info_plus-9.0.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"path_provider_windows","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"share_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/share_plus-12.0.1/","native_build":true,"dependencies":["url_launcher_windows"],"dev_dependency":false},{"name":"shared_preferences_windows","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/","native_build":false,"dependencies":["path_provider_windows"],"dev_dependency":false},{"name":"url_launcher_windows","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.5/","native_build":true,"dependencies":[],"dev_dependency":false}],"web":[{"name":"audio_session","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/audio_session-0.2.3/","dependencies":[],"dev_dependency":false},{"name":"connectivity_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/connectivity_plus-7.0.0/","dependencies":[],"dev_dependency":false},{"name":"flutter_native_splash","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/flutter_native_splash-2.4.7/","dependencies":[],"dev_dependency":false},{"name":"just_audio_web","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/just_audio_web-0.4.16/","dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/package_info_plus-9.0.0/","dependencies":[],"dev_dependency":false},{"name":"share_plus","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/share_plus-12.0.1/","dependencies":["url_launcher_web"],"dev_dependency":false},{"name":"shared_preferences_web","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.3/","dependencies":[],"dev_dependency":false},{"name":"url_launcher_web","path":"/Users/pyrossh/.pub-cache/hosted/pub.dev/url_launcher_web-2.4.2/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"app_review","dependencies":[]},{"name":"audio_session","dependencies":[]},{"name":"connectivity_plus","dependencies":[]},{"name":"flutter_native_splash","dependencies":[]},{"name":"integration_test","dependencies":[]},{"name":"just_audio","dependencies":["just_audio_web","audio_session","path_provider"]},{"name":"just_audio_web","dependencies":[]},{"name":"package_info_plus","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"share_plus","dependencies":["url_launcher_web","url_launcher_windows","url_launcher_linux"]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]},{"name":"url_launcher","dependencies":["url_launcher_android","url_launcher_ios","url_launcher_linux","url_launcher_macos","url_launcher_web","url_launcher_windows"]},{"name":"url_launcher_android","dependencies":[]},{"name":"url_launcher_ios","dependencies":[]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]},{"name":"webview_flutter","dependencies":["webview_flutter_android","webview_flutter_wkwebview"]},{"name":"webview_flutter_android","dependencies":[]},{"name":"webview_flutter_wkwebview","dependencies":[]}],"date_created":"2026-03-22 09:38:40.134140","version":"3.41.4","swift_package_manager_enabled":{"ios":false,"macos":false}}
ios/Podfile.lock CHANGED
@@ -3,6 +3,8 @@ PODS:
3
3
  - Flutter
4
4
  - audio_session (0.0.1):
5
5
  - Flutter
6
+ - connectivity_plus (0.0.1):
7
+ - Flutter
6
8
  - Flutter (1.0.0)
7
9
  - flutter_native_splash (2.4.3):
8
10
  - Flutter
@@ -27,6 +29,7 @@ PODS:
27
29
  DEPENDENCIES:
28
30
  - app_review (from `.symlinks/plugins/app_review/ios`)
29
31
  - audio_session (from `.symlinks/plugins/audio_session/ios`)
32
+ - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
30
33
  - Flutter (from `Flutter`)
31
34
  - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
32
35
  - integration_test (from `.symlinks/plugins/integration_test/ios`)
@@ -42,6 +45,8 @@ EXTERNAL SOURCES:
42
45
  :path: ".symlinks/plugins/app_review/ios"
43
46
  audio_session:
44
47
  :path: ".symlinks/plugins/audio_session/ios"
48
+ connectivity_plus:
49
+ :path: ".symlinks/plugins/connectivity_plus/ios"
45
50
  Flutter:
46
51
  :path: Flutter
47
52
  flutter_native_splash:
@@ -64,6 +69,7 @@ EXTERNAL SOURCES:
64
69
  SPEC CHECKSUMS:
65
70
  app_review: f75e3250ef5694e328a5ecb3f4c445bc7b32d821
66
71
  audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0
72
+ connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
67
73
  Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
68
74
  flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
69
75
  integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
lib/app.dart CHANGED
@@ -1,3 +1,4 @@
1
+ import "package:async_redux/async_redux.dart";
1
2
  import "package:flutter/material.dart";
2
3
  import "package:go_router/go_router.dart";
3
4
  import "package:only_bible_app/l10n/app_localizations.dart";
@@ -6,114 +7,150 @@ import "package:only_bible_app/screens/book_select_screen.dart";
6
7
  import "package:only_bible_app/screens/chapter_select_screen.dart";
7
8
  import "package:only_bible_app/screens/chapter_view_screen.dart";
8
9
  import "package:only_bible_app/screens/webview_screen.dart";
9
- import "package:only_bible_app/store/state.dart";
10
+ import "package:only_bible_app/store/app_state.dart";
11
+ import "package:only_bible_app/store/app_persistor.dart";
10
12
  import "package:only_bible_app/theme.dart";
11
13
  import "package:only_bible_app/utils.dart";
12
14
 
13
15
  final globalNavigatorKey = GlobalKey<NavigatorState>();
16
+ final persistor = AppPersistor();
17
+ final Store<AppState> store = Store<AppState>(
18
+ initialState: const AppState(),
19
+ persistor: persistor,
20
+ );
21
+ final GoRouter router = buildRouter();
14
22
 
23
+ GoRouter buildRouter() {
24
+ final s = store.state;
15
- final router = GoRouter(
25
+ return GoRouter(
16
- navigatorKey: globalNavigatorKey,
26
+ navigatorKey: globalNavigatorKey,
17
- initialLocation: firstOpenAtom.value
27
+ initialLocation: s.firstOpen
18
- ? "/bible"
28
+ ? "/bible"
19
- : "/chapter/${Uri.encodeComponent(bibleNameAtom.value)}/${savedBookAtom.value}/${savedChapterAtom.value}",
29
+ : "/chapter/${Uri.encodeComponent(s.bibleName)}/${s.savedBook}/${s.savedChapter}",
20
- routes: [
30
+ routes: [
21
- GoRoute(
31
+ GoRoute(
22
- path: "/bible",
32
+ path: "/bible",
23
- pageBuilder: (context, state) => const NoTransitionPage(
33
+ pageBuilder: (context, state) => const NoTransitionPage(
24
- child: BibleSelectScreen(),
34
+ child: BibleSelectScreen(),
35
+ ),
25
36
  ),
37
+ GoRoute(
38
+ path: "/chapter/:bibleName/:bookIndex/:chapterIndex",
39
+ pageBuilder: (context, state) {
40
+ final bibleName =
41
+ Uri.decodeComponent(state.pathParameters["bibleName"]!);
42
+ final bookIndex = int.parse(state.pathParameters["bookIndex"]!);
43
+ final chapterIndex = int.parse(state.pathParameters["chapterIndex"]!);
44
+ final slideDir = state.extra as TextDirection?;
45
+ if (slideDir != null) {
46
+ return CustomTransitionPage(
47
+ key: state.pageKey,
48
+ child: ChapterViewScreen(
49
+ bibleName: bibleName,
50
+ bookIndex: bookIndex,
51
+ chapterIndex: chapterIndex,
26
- ),
52
+ ),
27
- GoRoute(
53
+ transitionsBuilder:
28
- path: "/chapter/:bibleName/:bookIndex/:chapterIndex",
29
- pageBuilder: (context, state) {
54
+ (context, animation, secondaryAnimation, child) {
55
+ const begin = Offset(1.0, 0.0);
30
- final bibleName =
56
+ const end = Offset.zero;
31
- Uri.decodeComponent(state.pathParameters["bibleName"]!);
57
+ const curve = Curves.ease;
32
- final bookIndex = int.parse(state.pathParameters["bookIndex"]!);
33
- final chapterIndex = int.parse(state.pathParameters["chapterIndex"]!);
34
- final slideDir = state.extra as TextDirection?;
58
+ final tween = Tween(begin: begin, end: end)
59
+ .chain(CurveTween(curve: curve));
35
- if (slideDir != null) {
60
+ return SlideTransition(
61
+ textDirection: slideDir,
62
+ position: animation.drive(tween),
63
+ child: child,
64
+ );
65
+ },
66
+ );
67
+ }
36
- return CustomTransitionPage(
68
+ return NoTransitionPage(
37
69
  key: state.pageKey,
38
70
  child: ChapterViewScreen(
39
71
  bibleName: bibleName,
40
72
  bookIndex: bookIndex,
41
73
  chapterIndex: chapterIndex,
42
74
  ),
43
- transitionsBuilder:
44
- (context, animation, secondaryAnimation, child) {
45
- const begin = Offset(1.0, 0.0);
46
- const end = Offset.zero;
47
- const curve = Curves.ease;
48
- final tween =
49
- Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
50
- return SlideTransition(
51
- textDirection: slideDir,
52
- position: animation.drive(tween),
53
- child: child,
54
- );
55
- },
56
75
  );
76
+ },
77
+ ),
78
+ GoRoute(
79
+ path: "/books/:bibleName",
80
+ pageBuilder: (context, state) {
81
+ final bibleName =
82
+ Uri.decodeComponent(state.pathParameters["bibleName"]!);
83
+ return NoTransitionPage(
84
+ child: BookSelectScreen(bibleName: bibleName),
85
+ );
86
+ },
87
+ ),
88
+ GoRoute(
89
+ path: "/chapters/:bibleName/:bookIndex",
90
+ pageBuilder: (context, state) {
91
+ final bibleName =
92
+ Uri.decodeComponent(state.pathParameters["bibleName"]!);
93
+ final bookIndex = int.parse(state.pathParameters["bookIndex"]!);
94
+ return NoTransitionPage(
95
+ child:
96
+ ChapterSelectScreen(bibleName: bibleName, bookIndex: bookIndex),
97
+ );
98
+ },
99
+ ),
100
+ GoRoute(
101
+ path: "/webview",
102
+ pageBuilder: (context, state) {
103
+ final url = state.extra as String;
104
+ return NoTransitionPage(
105
+ child: WebViewScreen(url: url),
106
+ );
107
+ },
108
+ ),
109
+ ],
110
+ );
57
- }
111
+ }
58
- return NoTransitionPage(
59
- key: state.pageKey,
60
- child: ChapterViewScreen(
61
- bibleName: bibleName,
62
- bookIndex: bookIndex,
63
- chapterIndex: chapterIndex,
64
- ),
65
- );
66
- },
67
- ),
68
- GoRoute(
69
- path: "/books/:bibleName",
70
- pageBuilder: (context, state) {
71
- final bibleName =
72
- Uri.decodeComponent(state.pathParameters["bibleName"]!);
73
- return NoTransitionPage(
74
- child: BookSelectScreen(bibleName: bibleName),
75
- );
76
- },
77
- ),
78
- GoRoute(
79
- path: "/chapters/:bibleName/:bookIndex",
80
- pageBuilder: (context, state) {
81
- final bibleName =
82
- Uri.decodeComponent(state.pathParameters["bibleName"]!);
83
- final bookIndex = int.parse(state.pathParameters["bookIndex"]!);
84
- return NoTransitionPage(
85
- child:
86
- ChapterSelectScreen(bibleName: bibleName, bookIndex: bookIndex),
87
- );
88
- },
89
- ),
90
- GoRoute(
91
- path: "/webview",
92
- pageBuilder: (context, state) {
93
- final url = state.extra as String;
94
- return NoTransitionPage(
95
- child: WebViewScreen(url: url),
96
- );
97
- },
98
- ),
99
- ],
100
- );
101
112
 
102
113
  class App extends StatelessWidget {
103
114
  const App({super.key});
104
115
 
105
116
  @override
106
117
  Widget build(BuildContext context) {
107
- return MaterialApp.router(
118
+ return StoreProvider<AppState>(
119
+ store: store,
120
+ child: StoreConnector<AppState, _AppVm>(
121
+ vm: () => _AppVmFactory(),
122
+ builder: (context, vm) => MaterialApp.router(
108
- routerConfig: router,
123
+ routerConfig: router,
109
- onGenerateTitle: (context) => context.l.title,
124
+ onGenerateTitle: (context) => context.l.title,
110
- localizationsDelegates: AppLocalizations.localizationsDelegates,
125
+ localizationsDelegates: AppLocalizations.localizationsDelegates,
111
- supportedLocales: AppLocalizations.supportedLocales,
126
+ supportedLocales: AppLocalizations.supportedLocales,
112
- debugShowCheckedModeBanner: false,
127
+ debugShowCheckedModeBanner: false,
113
- themeMode: darkModeAtom.watch(context) ? ThemeMode.dark : ThemeMode.light,
128
+ themeMode: vm.darkMode ? ThemeMode.dark : ThemeMode.light,
114
- theme: lightTheme,
129
+ theme: lightTheme,
115
- darkTheme: darkTheme,
130
+ darkTheme: darkTheme,
116
- locale: Locale(languageCodeAtom.watch(context)),
131
+ locale: Locale(vm.languageCode),
132
+ ),
133
+ ),
134
+ );
135
+ }
136
+ }
137
+
138
+ class _AppVm extends Vm {
139
+ final bool darkMode;
140
+ final String languageCode;
141
+
142
+ _AppVm({
143
+ required this.darkMode,
144
+ required this.languageCode,
145
+ }) : super(equals: [darkMode, languageCode]);
146
+ }
147
+
148
+ class _AppVmFactory extends VmFactory<AppState, App, _AppVm> {
149
+ @override
150
+ _AppVm fromStore() {
151
+ return _AppVm(
152
+ darkMode: state.darkMode,
153
+ languageCode: state.languageCode,
117
154
  );
118
155
  }
119
156
  }
lib/main.dart CHANGED
@@ -8,9 +8,9 @@ import "package:only_bible_app/app.dart";
8
8
  import "package:only_bible_app/dialog.dart";
9
9
  import "package:only_bible_app/env.dart";
10
10
  import "package:only_bible_app/navigation.dart";
11
- import "package:only_bible_app/store/state.dart";
12
11
 
13
12
  void main() async {
13
+ // await store.persistor.readState();
14
14
  // FlutterError.onError = (errorDetails) {
15
15
  // SchedulerBinding.instance.addPostFrameCallback((d) {
16
16
  // showReportError(
@@ -34,13 +34,13 @@ void main() async {
34
34
  // widgetsBinding: WidgetsFlutterBinding.ensureInitialized(),
35
35
  // );
36
36
  usePathUrlStrategy();
37
+ WidgetsFlutterBinding.ensureInitialized();
37
38
  FlutterAzureTts.init(
38
39
  subscriptionKey: "a9d2d78796924a2a9df2b6d5c1c4a576",
39
40
  region: "centralindia",
40
41
  withLogs: true,
41
42
  );
42
- // await initState();
43
- // updateStatusBar(darkModeAtom.value);
43
+ updateStatusBar(store.state.darkMode);
44
44
  runApp(const App());
45
45
  // FlutterNativeSplash.remove();
46
46
  }
lib/navigation.dart CHANGED
@@ -1,4 +1,3 @@
1
- import "package:atoms_state/atoms_state.dart";
2
1
  import "package:flutter/material.dart";
3
2
  import "package:flutter/services.dart";
4
3
  import "package:app_review/app_review.dart";
@@ -6,32 +5,11 @@ import "package:go_router/go_router.dart";
6
5
  import "package:only_bible_app/models.dart";
7
6
  import "package:only_bible_app/sheets/settings_sheet.dart";
8
7
  import "package:only_bible_app/store/actions.dart";
9
- import "package:only_bible_app/store/state.dart";
8
+ import "package:only_bible_app/store/app_state.dart";
9
+ import "package:only_bible_app/app.dart";
10
10
  import "package:only_bible_app/utils.dart";
11
11
  import "package:share_plus/share_plus.dart";
12
12
 
13
- final actionsShownAtom = Atom(
14
- key: "actionsShown",
15
- initialState: false,
16
- reducer: (state, action) {
17
- if (action is SetActionsShown) {
18
- return action.value;
19
- }
20
- return state;
21
- },
22
- );
23
-
24
- final highlightsShownAtom = Atom(
25
- key: "highlightsShown",
26
- initialState: false,
27
- reducer: (state, action) {
28
- if (action is SetHighlightsShown) {
29
- return action.value;
30
- }
31
- return state;
32
- },
33
- );
34
-
35
13
  String _chapterPath(String bibleName, int book, int chapter) {
36
14
  return "/chapter/${Uri.encodeComponent(bibleName)}/$book/$chapter";
37
15
  }
@@ -65,8 +43,12 @@ void pushBookChapter(
65
43
  int chapter,
66
44
  TextDirection? dir,
67
45
  ) {
68
- dispatch(UpdateChapter(book, chapter));
46
+ store.dispatch(UpdateChapterAction(book, chapter));
47
+ if (store.state.isPlaying) {
48
+ AppState.player.pause();
49
+ store.dispatch(SetPlayingAction(false));
50
+ }
69
- clearEvents(context);
51
+ hideActions(context);
70
52
  context.push(_chapterPath(bibleName, book, chapter), extra: dir);
71
53
  }
72
54
 
@@ -76,8 +58,12 @@ void replaceBookChapter(
76
58
  int book,
77
59
  int chapter,
78
60
  ) {
79
- dispatch(UpdateChapter(book, chapter));
61
+ store.dispatch(UpdateChapterAction(book, chapter));
62
+ if (store.state.isPlaying) {
63
+ AppState.player.pause();
64
+ store.dispatch(SetPlayingAction(false));
65
+ }
80
- clearEvents(context);
66
+ hideActions(context);
81
67
  context.go(_chapterPath(bibleName, book, chapter));
82
68
  }
83
69
 
@@ -169,7 +155,7 @@ Future<void> updateCurrentBible(
169
155
  int chapter,
170
156
  ) async {
171
157
  hideActions(context);
172
- dispatch(UpdateBible(name, code));
158
+ store.dispatch(UpdateBibleAction(name, code));
173
159
  context.go(_chapterPath(name, book, chapter));
174
160
  }
175
161
 
@@ -201,7 +187,13 @@ Future<void> shareVerses(
201
187
  final version = context.currentLang.languageCode == "en" ? "KJV" : "";
202
188
  final title = "$name $chapter:$versesThrough $version";
203
189
  final text = verses.map((e) => e.text).join("\n");
190
+ await SharePlus.instance.share(
191
+ ShareParams(
192
+ title: title,
193
+ subject: title,
204
- await Share.share("$title\n$text", subject: title);
194
+ text: "$title\n$text",
195
+ ),
196
+ );
205
197
  hideActions(context);
206
198
  }
207
199
 
@@ -217,24 +209,20 @@ void showSettings(BuildContext context, Bible bible) {
217
209
  }
218
210
 
219
211
  void showActions(BuildContext context, Bible bible) {
220
- if (!actionsShownAtom.value) {
221
- dispatch(const SetActionsShown(true));
212
+ store.dispatch(SetActionsShownAction(true));
222
- }
223
213
  }
224
214
 
225
215
  void hideActions(BuildContext context) {
226
- if (actionsShownAtom.value) {
227
- dispatch(const SetActionsShown(false));
216
+ store.dispatch(SetActionsShownAction(false));
228
- dispatch(const ClearSelectedVerses());
217
+ store.dispatch(ClearSelectedVersesAction());
229
- }
230
218
  }
231
219
 
232
220
  void showHighlights(BuildContext context) {
233
- dispatch(const SetHighlightsShown(true));
221
+ store.dispatch(SetHighlightsShownAction(true));
234
222
  }
235
223
 
236
224
  void hideHighlights(BuildContext context) {
237
- if (highlightsShownAtom.value) {
225
+ if (store.state.highlightsShown) {
238
- dispatch(const SetHighlightsShown(false));
226
+ store.dispatch(SetHighlightsShownAction(false));
239
227
  }
240
228
  }
lib/screens/bible_select_screen.dart CHANGED
@@ -1,8 +1,7 @@
1
- import "package:atoms_state/atoms_state.dart";
2
1
  import "package:flutter/material.dart";
3
2
  import "package:only_bible_app/navigation.dart";
4
3
  import "package:only_bible_app/store/actions.dart";
5
- import "package:only_bible_app/store/state.dart";
4
+ import "package:only_bible_app/app.dart";
6
5
  import "package:only_bible_app/utils.dart";
7
6
  import "package:only_bible_app/widgets/scaffold_menu.dart";
8
7
  import "package:only_bible_app/widgets/sliver_heading.dart";
@@ -17,7 +16,10 @@ class BibleSelectScreen extends StatelessWidget {
17
16
  child: CustomScrollView(
18
17
  physics: const BouncingScrollPhysics(),
19
18
  slivers: [
19
+ SliverHeading(
20
- SliverHeading(title: context.l.bibleSelectTitle, showClose: !firstOpenAtom.value),
20
+ title: context.l.bibleSelectTitle,
21
+ showClose: !store.state.firstOpen,
22
+ ),
21
23
  SliverTileGrid(
22
24
  listType: ListType.extraLarge,
23
25
  children: List.of(
@@ -33,15 +35,15 @@ class BibleSelectScreen extends StatelessWidget {
33
35
  )
34
36
  : Text(l.languageTitle, textScaleFactor: 1.1),
35
37
  onPressed: () {
36
- if (firstOpenAtom.value) {
38
+ if (store.state.firstOpen) {
37
- dispatch(FirstOpenDone());
39
+ store.dispatch(FirstOpenDoneAction());
38
40
  }
39
41
  updateCurrentBible(
40
42
  context,
41
43
  l.languageTitle,
42
44
  l.localeName,
43
- savedBookAtom.value,
45
+ store.state.savedBook,
44
- savedChapterAtom.value,
46
+ store.state.savedChapter,
45
47
  );
46
48
  },
47
49
  );
lib/screens/book_select_screen.dart CHANGED
@@ -1,7 +1,6 @@
1
1
  import "package:flutter/material.dart";
2
2
  import "package:go_router/go_router.dart";
3
3
  import "package:only_bible_app/navigation.dart";
4
- import "package:only_bible_app/store/state.dart";
5
4
  import "package:only_bible_app/utils.dart";
6
5
  import "package:only_bible_app/widgets/scaffold_menu.dart";
7
6
  import "package:only_bible_app/widgets/sliver_heading.dart";
@@ -24,7 +23,7 @@ class BookSelectScreen extends StatelessWidget {
24
23
  @override
25
24
  Widget build(BuildContext context) {
26
25
  return FutureBuilder(
27
- future: bibleAtom.getValue(bibleName),
26
+ future: loadBible(bibleName),
28
27
  builder: (context, state) {
29
28
  return state.when(
30
29
  loading: () => ColoredBox(
lib/screens/chapter_select_screen.dart CHANGED
@@ -1,7 +1,6 @@
1
1
  import "package:flutter/material.dart";
2
2
  import "package:only_bible_app/models.dart";
3
3
  import "package:only_bible_app/navigation.dart";
4
- import "package:only_bible_app/store/state.dart";
5
4
  import "package:only_bible_app/utils.dart";
6
5
  import "package:only_bible_app/widgets/scaffold_menu.dart";
7
6
  import "package:only_bible_app/widgets/sliver_tile_grid.dart";
@@ -17,7 +16,7 @@ class ChapterSelectScreen extends StatelessWidget {
17
16
  @override
18
17
  Widget build(BuildContext context) {
19
18
  return FutureBuilder(
20
- future: bibleAtom.getValue(bibleName),
19
+ future: loadBible(bibleName),
21
20
  builder: (context, state) {
22
21
  return state.when(
23
22
  loading: () => ColoredBox(
lib/screens/chapter_view_screen.dart CHANGED
@@ -1,9 +1,8 @@
1
1
  import "package:flutter/material.dart";
2
2
  import "package:only_bible_app/models.dart";
3
- import "package:only_bible_app/navigation.dart";
4
3
  import "package:only_bible_app/sheets/actions_sheet.dart";
5
4
  import "package:only_bible_app/sheets/highlight_sheet.dart";
6
- import "package:only_bible_app/store/state.dart";
5
+ import "package:only_bible_app/store/actions.dart";
7
6
  import "package:only_bible_app/utils.dart";
8
7
  import "package:only_bible_app/widgets/chapter_app_bar.dart";
9
8
  import "package:only_bible_app/widgets/verses_view.dart";
@@ -23,7 +22,7 @@ class ChapterViewScreen extends StatelessWidget {
23
22
  @override
24
23
  Widget build(BuildContext context) {
25
24
  return FutureBuilder(
26
- future: bibleAtom.getValue(bibleName),
25
+ future: loadBible(bibleName),
27
26
  builder: (context, state) {
28
27
  return state.when(
29
28
  loading: () => ColoredBox(
@@ -42,9 +41,9 @@ class ChapterViewScreen extends StatelessWidget {
42
41
  child: Column(
43
42
  children: [
44
43
  Expanded(child: VersesView(bible: bible, chapter: chapter)),
45
- if (highlightsShownAtom.watch(context))
44
+ if (context.state.highlightsShown)
46
45
  const HighlightSheet()
47
- else if (actionsShownAtom.watch(context))
46
+ else if (context.state.actionsShown)
48
47
  ActionsSheet(bible: bible),
49
48
  ],
50
49
  ),
lib/sheets/actions_sheet.dart CHANGED
@@ -1,7 +1,8 @@
1
1
  import "package:flutter/material.dart";
2
2
  import "package:only_bible_app/models.dart";
3
3
  import "package:only_bible_app/navigation.dart";
4
+ import "package:only_bible_app/app.dart";
4
- import "package:only_bible_app/store/state.dart";
5
+ import "package:only_bible_app/store/actions.dart";
5
6
  import "package:only_bible_app/utils.dart";
6
7
 
7
8
  class ActionsSheet extends StatelessWidget {
@@ -11,10 +12,10 @@ class ActionsSheet extends StatelessWidget {
11
12
 
12
13
  @override
13
14
  Widget build(BuildContext context) {
14
- final iconColor = darkModeAtom.value
15
+ final iconColor = store.state.darkMode
15
16
  ? Colors.white.withOpacity(0.9)
16
17
  : Colors.black.withOpacity(0.9);
17
- final audioIcon = isPlaying.watch(context)
18
+ final audioIcon = context.state.isPlaying
18
19
  ? Icons.pause_circle_outline
19
20
  : Icons.play_circle_outline;
20
21
  return Container(
@@ -26,7 +27,12 @@ class ActionsSheet extends StatelessWidget {
26
27
  children: [
27
28
  IconButton(
28
29
  padding: EdgeInsets.zero,
30
+ onPressed: () {
29
- onPressed: () => removeHighlight(context),
31
+ store.dispatch(RemoveHighlightAction(
32
+ List<Verse>.from(store.state.selectedVerses),
33
+ ));
34
+ hideActions(context);
35
+ },
30
36
  icon: Icon(Icons.cancel_outlined, size: 28, color: iconColor),
31
37
  ),
32
38
  IconButton(
@@ -37,14 +43,14 @@ class ActionsSheet extends StatelessWidget {
37
43
  IconButton(
38
44
  padding: EdgeInsets.zero,
39
45
  onPressed: () {
40
- onPlay(context, bible);
46
+ store.state.onPlay(context, bible);
41
47
  },
42
48
  icon: Icon(audioIcon, size: 34, color: iconColor),
43
49
  ),
44
50
  IconButton(
45
51
  padding: EdgeInsets.zero,
46
52
  onPressed: () =>
47
- shareVerses(context, bible, selectedVersesAtom.value),
53
+ shareVerses(context, bible, store.state.selectedVerses),
48
54
  icon: Icon(Icons.share_outlined, size: 34, color: iconColor),
49
55
  ),
50
56
  ],
lib/sheets/highlight_sheet.dart CHANGED
@@ -1,5 +1,8 @@
1
1
  import "package:flutter/material.dart";
2
+ import "package:only_bible_app/app.dart";
3
+ import "package:only_bible_app/models.dart";
4
+ import "package:only_bible_app/navigation.dart";
2
- import "package:only_bible_app/store/state.dart";
5
+ import "package:only_bible_app/store/actions.dart";
3
6
  import "package:only_bible_app/theme.dart";
4
7
  import "package:only_bible_app/utils.dart";
5
8
  import "package:only_bible_app/widgets/highlight_button.dart";
@@ -9,11 +12,15 @@ class HighlightSheet extends StatelessWidget {
9
12
 
10
13
  @override
11
14
  Widget build(BuildContext context) {
12
- final iconColor = darkModeAtom.value
15
+ final iconColor = store.state.darkMode
13
16
  ? Colors.white.withOpacity(0.9)
14
17
  : Colors.black.withOpacity(0.9);
15
18
  void onHighlight(int index) {
19
+ store.dispatch(SetHighlightAction(
20
+ List<Verse>.from(store.state.selectedVerses),
21
+ index,
22
+ ));
16
- setHighlight(context, index);
23
+ hideActions(context);
17
24
  }
18
25
 
19
26
  return Container(
@@ -25,27 +32,38 @@ class HighlightSheet extends StatelessWidget {
25
32
  children: [
26
33
  IconButton(
27
34
  padding: EdgeInsets.zero,
35
+ onPressed: () {
36
+ store.dispatch(
37
+ RemoveHighlightAction(
28
- onPressed: () => removeHighlight(context),
38
+ List<Verse>.from(store.state.selectedVerses),
39
+ ),
40
+ );
41
+ hideActions(context);
42
+ },
29
43
  icon: Icon(Icons.cancel_outlined, size: 28, color: iconColor),
30
44
  ),
31
45
  HighlightButton(
32
46
  index: 0,
47
+ color:
33
- color: darkModeAtom.value ? darkHighlights[0] : lightHighlights[0],
48
+ store.state.darkMode ? darkHighlights[0] : lightHighlights[0],
34
49
  onHighlightSelected: onHighlight,
35
50
  ),
36
51
  HighlightButton(
37
52
  index: 1,
53
+ color:
38
- color: darkModeAtom.value ? darkHighlights[1] : lightHighlights[1],
54
+ store.state.darkMode ? darkHighlights[1] : lightHighlights[1],
39
55
  onHighlightSelected: onHighlight,
40
56
  ),
41
57
  HighlightButton(
42
58
  index: 2,
59
+ color:
43
- color: darkModeAtom.value ? darkHighlights[2] : lightHighlights[2],
60
+ store.state.darkMode ? darkHighlights[2] : lightHighlights[2],
44
61
  onHighlightSelected: onHighlight,
45
62
  ),
46
63
  HighlightButton(
47
64
  index: 3,
65
+ color:
48
- color: darkModeAtom.value ? darkHighlights[3] : lightHighlights[3],
66
+ store.state.darkMode ? darkHighlights[3] : lightHighlights[3],
49
67
  onHighlightSelected: onHighlight,
50
68
  ),
51
69
  ],
lib/sheets/settings_sheet.dart CHANGED
@@ -1,9 +1,8 @@
1
- import "package:atoms_state/atoms_state.dart";
2
1
  import "package:flutter/material.dart";
3
2
  import "package:only_bible_app/models.dart";
4
3
  import "package:only_bible_app/navigation.dart";
5
4
  import "package:only_bible_app/store/actions.dart";
6
- import "package:only_bible_app/store/state.dart";
5
+ import "package:only_bible_app/app.dart";
7
6
  import "package:only_bible_app/utils.dart";
8
7
  import "package:settings_ui/settings_ui.dart";
9
8
 
@@ -25,26 +24,33 @@ class SettingsSheet extends StatelessWidget {
25
24
  ),
26
25
  sections: [
27
26
  SettingsSection(
27
+ title: Text(
28
+ context.l.settingsTitle,
28
- title: Text(context.l.settingsTitle, style: context.theme.textTheme.headlineMedium),
29
+ style: context.theme.textTheme.headlineMedium,
30
+ ),
29
31
  margin: const EdgeInsetsDirectional.symmetric(horizontal: 20),
30
32
  tiles: [
31
33
  SettingsTile.navigation(
34
+ leading:
32
- leading: const Icon(Icons.book_outlined, color: Colors.blueAccent),
35
+ const Icon(Icons.book_outlined, color: Colors.blueAccent),
33
36
  title: Text(context.l.bibleTitle),
34
37
  value: Text(bible.name),
35
38
  onPressed: changeBible,
36
39
  ),
37
40
  SettingsTile.navigation(
41
+ leading:
38
- leading: const Icon(Icons.color_lens_outlined, color: Colors.green),
42
+ const Icon(Icons.color_lens_outlined, color: Colors.green),
39
43
  title: Text(context.l.themeTitle),
40
44
  trailing: ToggleButtons(
41
45
  onPressed: (int index) {
42
- dispatch(ToggleDarkMode());
46
+ store.dispatch(ToggleDarkModeAction());
43
47
  },
44
48
  highlightColor: Colors.transparent,
45
49
  borderColor: Colors.grey,
46
50
  borderRadius: const BorderRadius.all(Radius.circular(25)),
51
+ selectedColor: store.state.darkMode
52
+ ? Colors.lightBlue.shade300
47
- selectedColor: darkModeAtom.value ? Colors.lightBlue.shade300 : Colors.yellowAccent.shade700,
53
+ : Colors.yellowAccent.shade700,
48
54
  selectedBorderColor: Colors.grey,
49
55
  color: Colors.grey,
50
56
  fillColor: Colors.transparent,
@@ -52,7 +58,7 @@ class SettingsSheet extends StatelessWidget {
52
58
  minHeight: 36.0,
53
59
  minWidth: 50.0,
54
60
  ),
55
- isSelected: [!darkModeAtom.value, darkModeAtom.value],
61
+ isSelected: [!store.state.darkMode, store.state.darkMode],
56
62
  children: const [
57
63
  Icon(Icons.light_mode),
58
64
  Icon(Icons.dark_mode),
@@ -61,37 +67,61 @@ class SettingsSheet extends StatelessWidget {
61
67
  ),
62
68
  SettingsTile(
63
69
  title: Text(context.l.incrementFontTitle),
70
+ leading: Icon(
71
+ Icons.font_download,
64
- leading: Icon(Icons.font_download, color: context.theme.colorScheme.onSurface),
72
+ color: context.theme.colorScheme.onSurface,
73
+ ),
65
74
  trailing: IconButton(
66
- onPressed: () => dispatch(const UpdateTextScale(0.1)),
75
+ onPressed: () => store.dispatch(UpdateTextScaleAction(0.1)),
76
+ icon: const Icon(
67
- icon: const Icon(Icons.add_circle_outline, size: 32, color: Colors.redAccent),
77
+ Icons.add_circle_outline,
78
+ size: 32,
79
+ color: Colors.redAccent,
80
+ ),
68
81
  ),
69
82
  ),
70
83
  SettingsTile(
71
84
  title: Text(context.l.decrementFontTitle),
85
+ leading: Icon(
86
+ Icons.font_download,
72
- leading: Icon(Icons.font_download, color: context.theme.colorScheme.onSurface),
87
+ color: context.theme.colorScheme.onSurface,
88
+ ),
73
89
  trailing: IconButton(
74
- onPressed: () => dispatch(const UpdateTextScale(-0.1)),
90
+ onPressed: () => store.dispatch(UpdateTextScaleAction(-0.1)),
91
+ icon: const Icon(
75
- icon: const Icon(Icons.remove_circle_outline, size: 32, color: Colors.blueAccent),
92
+ Icons.remove_circle_outline,
93
+ size: 32,
94
+ color: Colors.blueAccent,
95
+ ),
76
96
  ),
77
97
  ),
78
98
  SettingsTile.switchTile(
79
- initialValue: boldFontAtom.watch(context),
99
+ initialValue: context.state.boldFont,
100
+ leading: Icon(
101
+ Icons.format_bold,
80
- leading: Icon(Icons.format_bold, color: context.theme.colorScheme.onSurface),
102
+ color: context.theme.colorScheme.onSurface,
103
+ ),
81
104
  title: Text(context.l.boldFontTitle),
82
- onToggle: (value) => dispatch(ToggleBoldFont()),
105
+ onToggle: (value) => store.dispatch(ToggleBoldFontAction()),
83
106
  ),
84
107
  SettingsTile.switchTile(
85
- initialValue: engTitlesAtom.watch(context),
108
+ initialValue: context.state.engTitles,
109
+ leading:
86
- leading: Icon(Icons.abc, color: context.theme.colorScheme.onSurface),
110
+ Icon(Icons.abc, color: context.theme.colorScheme.onSurface),
87
111
  title: Text(context.l.engTitles),
88
- onToggle: (value) => dispatch(ToggleEngTitles()),
112
+ onToggle: (value) => store.dispatch(ToggleEngTitlesAction()),
89
113
  ),
90
114
  ],
91
115
  ),
92
116
  SettingsSection(
117
+ title: Text(
118
+ context.l.aboutUsTitle,
93
- title: Text(context.l.aboutUsTitle, style: context.theme.textTheme.headlineMedium),
119
+ style: context.theme.textTheme.headlineMedium,
120
+ ),
94
- margin: const EdgeInsetsDirectional.symmetric(horizontal: 20, vertical: 20),
121
+ margin: const EdgeInsetsDirectional.symmetric(
122
+ horizontal: 20,
123
+ vertical: 20,
124
+ ),
95
125
  tiles: [
96
126
  SettingsTile.navigation(
97
127
  leading: const Icon(Icons.policy_outlined, color: Colors.brown),
@@ -99,12 +129,16 @@ class SettingsSheet extends StatelessWidget {
99
129
  onPressed: showPrivacyPolicy,
100
130
  ),
101
131
  SettingsTile.navigation(
132
+ leading: const Icon(
102
- leading: const Icon(Icons.description_outlined, color: Colors.blueGrey),
133
+ Icons.description_outlined,
134
+ color: Colors.blueGrey,
135
+ ),
103
136
  title: Text(context.l.termsAndConditionsTitle),
104
137
  onPressed: showTermsAndConditions,
105
138
  ),
106
139
  SettingsTile.navigation(
140
+ leading:
107
- leading: const Icon(Icons.share_outlined, color: Colors.blueAccent),
141
+ const Icon(Icons.share_outlined, color: Colors.blueAccent),
108
142
  title: Text(context.l.shareAppTitle),
109
143
  onPressed: shareAppLink,
110
144
  ),
@@ -114,7 +148,10 @@ class SettingsSheet extends StatelessWidget {
114
148
  onPressed: rateApp,
115
149
  ),
116
150
  SettingsTile.navigation(
151
+ leading: Icon(
152
+ Icons.info_outline,
117
- leading: Icon(Icons.info_outline, color: context.theme.colorScheme.onSurface),
153
+ color: context.theme.colorScheme.onSurface,
154
+ ),
118
155
  title: Text(context.l.aboutUsTitle),
119
156
  onPressed: showAboutUs,
120
157
  ),
lib/store/actions.dart CHANGED
@@ -1,60 +1,142 @@
1
+ import "package:async_redux/async_redux.dart";
1
2
  import "package:only_bible_app/models.dart";
3
+ import "package:only_bible_app/store/app_state.dart";
2
4
 
3
- sealed class AppAction {}
4
-
5
- class FirstOpenDone implements AppAction {}
5
+ class FirstOpenDoneAction extends ReduxAction<AppState> {
6
+ @override
7
+ AppState reduce() => state.copy(firstOpen: false);
8
+ }
6
9
 
7
- class UpdateBible implements AppAction {
10
+ class UpdateBibleAction extends ReduxAction<AppState> {
8
11
  final String name;
9
12
  final String code;
10
13
 
11
- const UpdateBible(this.name, this.code);
14
+ UpdateBibleAction(this.name, this.code);
15
+
16
+ @override
17
+ AppState reduce() => state.copy(bibleName: name, languageCode: code);
12
18
  }
13
19
 
14
- class ToggleEngTitles implements AppAction {}
20
+ class ToggleEngTitlesAction extends ReduxAction<AppState> {
21
+ @override
22
+ AppState reduce() => state.copy(engTitles: !state.engTitles);
23
+ }
15
24
 
16
- class ToggleBoldFont implements AppAction {}
25
+ class ToggleBoldFontAction extends ReduxAction<AppState> {
26
+ @override
27
+ AppState reduce() => state.copy(boldFont: !state.boldFont);
28
+ }
17
29
 
18
- class ToggleDarkMode implements AppAction {}
30
+ class ToggleDarkModeAction extends ReduxAction<AppState> {
31
+ @override
32
+ AppState reduce() => state.copy(darkMode: !state.darkMode);
33
+ }
19
34
 
20
- class UpdateTextScale implements AppAction {
35
+ class UpdateTextScaleAction extends ReduxAction<AppState> {
21
36
  final double value;
22
37
 
23
- const UpdateTextScale(this.value);
38
+ UpdateTextScaleAction(this.value);
39
+
40
+ @override
41
+ AppState reduce() => state.copy(textScale: state.textScale + value);
24
42
  }
25
43
 
26
- class UpdateChapter implements AppAction {
44
+ class UpdateChapterAction extends ReduxAction<AppState> {
27
45
  final int book;
28
46
  final int chapter;
29
47
 
30
- const UpdateChapter(this.book, this.chapter);
48
+ UpdateChapterAction(this.book, this.chapter);
49
+
50
+ @override
51
+ AppState reduce() => state.copy(savedBook: book, savedChapter: chapter);
31
52
  }
32
53
 
33
- class SetPlaying implements AppAction {
54
+ class SetPlayingAction extends ReduxAction<AppState> {
34
55
  final bool value;
35
56
 
36
- const SetPlaying(this.value);
57
+ SetPlayingAction(this.value);
58
+
59
+ @override
60
+ AppState reduce() => state.copy(isPlaying: value);
37
61
  }
38
62
 
39
- class SelectVerse implements AppAction {
63
+ class SelectVerseAction extends ReduxAction<AppState> {
40
64
  final Verse verse;
41
65
 
42
- const SelectVerse(this.verse);
66
+ SelectVerseAction(this.verse);
67
+
68
+ @override
69
+ AppState reduce() {
70
+ final isSelected = state.selectedVerses.any(
71
+ (el) =>
72
+ el.book == verse.book &&
73
+ el.chapter == verse.chapter &&
74
+ el.index == verse.index,
75
+ );
76
+ if (isSelected) {
77
+ return state.copy(
78
+ selectedVerses: state.selectedVerses
79
+ .where((it) => it.index != verse.index)
80
+ .toList(),
81
+ );
82
+ } else {
83
+ return state.copy(
84
+ selectedVerses: [...state.selectedVerses, verse],
85
+ );
86
+ }
87
+ }
43
88
  }
44
89
 
45
-
46
- class ClearSelectedVerses implements AppAction {
90
+ class ClearSelectedVersesAction extends ReduxAction<AppState> {
91
+ @override
47
- const ClearSelectedVerses();
92
+ AppState reduce() => state.copy(selectedVerses: []);
48
93
  }
49
94
 
50
- class SetActionsShown implements AppAction {
95
+ class SetActionsShownAction extends ReduxAction<AppState> {
51
96
  final bool value;
52
97
 
53
- const SetActionsShown(this.value);
98
+ SetActionsShownAction(this.value);
99
+
100
+ @override
101
+ AppState reduce() => state.copy(actionsShown: value);
54
102
  }
55
103
 
56
- class SetHighlightsShown implements AppAction {
104
+ class SetHighlightsShownAction extends ReduxAction<AppState> {
57
105
  final bool value;
58
106
 
59
- const SetHighlightsShown(this.value);
107
+ SetHighlightsShownAction(this.value);
108
+
109
+ @override
110
+ AppState reduce() => state.copy(highlightsShown: value);
111
+ }
112
+
113
+ class SetHighlightAction extends ReduxAction<AppState> {
114
+ final List<Verse> verses;
115
+ final int colorIndex;
116
+
117
+ SetHighlightAction(this.verses, this.colorIndex);
118
+
119
+ @override
120
+ AppState reduce() {
121
+ final updated = Map<String, int>.from(state.highlights);
122
+ for (final v in verses) {
123
+ updated["${v.book}:${v.chapter}:${v.index}"] = colorIndex;
124
+ }
125
+ return state.copy(highlights: updated);
126
+ }
127
+ }
128
+
129
+ class RemoveHighlightAction extends ReduxAction<AppState> {
130
+ final List<Verse> verses;
131
+
132
+ RemoveHighlightAction(this.verses);
133
+
134
+ @override
135
+ AppState reduce() {
136
+ final updated = Map<String, int>.from(state.highlights);
137
+ for (final v in verses) {
138
+ updated.remove("${v.book}:${v.chapter}:${v.index}");
139
+ }
140
+ return state.copy(highlights: updated);
141
+ }
60
142
  }
lib/store/app_persistor.dart ADDED
@@ -0,0 +1,49 @@
1
+ import "dart:convert";
2
+ import "dart:io";
3
+
4
+ import "package:path_provider/path_provider.dart";
5
+ import "package:async_redux/async_redux.dart";
6
+ import "package:only_bible_app/store/app_state.dart";
7
+
8
+ class AppPersistor extends Persistor<AppState> {
9
+ static const _fileName = "app_state.json";
10
+
11
+ Future<File> get _file async {
12
+ final dir = await getApplicationDocumentsDirectory();
13
+ return File("${dir.path}/$_fileName");
14
+ }
15
+
16
+ @override
17
+ Future<AppState?> readState() async {
18
+ try {
19
+ final file = await _file;
20
+ if (await file.exists()) {
21
+ final contents = await file.readAsString();
22
+ final json = jsonDecode(contents) as Map<String, dynamic>;
23
+ return AppState.fromJson(json);
24
+ }
25
+ } catch (_) {}
26
+ return null;
27
+ }
28
+
29
+ @override
30
+ Future<void> deleteState() async {
31
+ final file = await _file;
32
+ if (await file.exists()) {
33
+ await file.delete();
34
+ }
35
+ }
36
+
37
+ @override
38
+ Future<void> persistDifference({
39
+ required AppState? lastPersistedState,
40
+ required AppState newState,
41
+ }) async {
42
+ final file = await _file;
43
+ final contents = jsonEncode(newState.toJson());
44
+ await file.writeAsString(contents);
45
+ }
46
+
47
+ @override
48
+ Duration? get throttle => const Duration(seconds: 1);
49
+ }
lib/store/app_state.dart ADDED
@@ -0,0 +1,182 @@
1
+ import "dart:developer";
2
+ import "package:flutter/material.dart";
3
+ import "package:just_audio/just_audio.dart";
4
+ import "package:only_bible_app/dialog.dart";
5
+ import "package:only_bible_app/models.dart";
6
+ import "package:only_bible_app/store/actions.dart";
7
+ import "package:only_bible_app/app.dart";
8
+ import "package:only_bible_app/store/buffer_audio_source.dart";
9
+ import "package:only_bible_app/theme.dart";
10
+ import "package:only_bible_app/utils.dart";
11
+
12
+ class AppState {
13
+ static final player = AudioPlayer();
14
+ static final noteTextController = TextEditingController();
15
+
16
+ final bool firstOpen;
17
+ final String languageCode;
18
+ final String bibleName;
19
+ final bool engTitles;
20
+ final bool boldFont;
21
+ final bool darkMode;
22
+ final double textScale;
23
+ final int savedBook;
24
+ final int savedChapter;
25
+ final bool isPlaying;
26
+ final List<Verse> selectedVerses;
27
+ final bool actionsShown;
28
+ final bool highlightsShown;
29
+ final Map<String, int> highlights;
30
+
31
+ const AppState({
32
+ this.firstOpen = true,
33
+ this.languageCode = "en",
34
+ this.bibleName = "English",
35
+ this.engTitles = false,
36
+ this.boldFont = false,
37
+ this.darkMode = false,
38
+ this.textScale = 0.0,
39
+ this.savedBook = 0,
40
+ this.savedChapter = 0,
41
+ this.isPlaying = false,
42
+ this.selectedVerses = const [],
43
+ this.actionsShown = false,
44
+ this.highlightsShown = false,
45
+ this.highlights = const {},
46
+ });
47
+
48
+ AppState copy({
49
+ bool? firstOpen,
50
+ String? languageCode,
51
+ String? bibleName,
52
+ bool? engTitles,
53
+ bool? boldFont,
54
+ bool? darkMode,
55
+ double? textScale,
56
+ int? savedBook,
57
+ int? savedChapter,
58
+ bool? isPlaying,
59
+ List<Verse>? selectedVerses,
60
+ bool? actionsShown,
61
+ bool? highlightsShown,
62
+ Map<String, int>? highlights,
63
+ }) {
64
+ return AppState(
65
+ firstOpen: firstOpen ?? this.firstOpen,
66
+ languageCode: languageCode ?? this.languageCode,
67
+ bibleName: bibleName ?? this.bibleName,
68
+ engTitles: engTitles ?? this.engTitles,
69
+ boldFont: boldFont ?? this.boldFont,
70
+ darkMode: darkMode ?? this.darkMode,
71
+ textScale: textScale ?? this.textScale,
72
+ savedBook: savedBook ?? this.savedBook,
73
+ savedChapter: savedChapter ?? this.savedChapter,
74
+ isPlaying: isPlaying ?? this.isPlaying,
75
+ selectedVerses: selectedVerses ?? this.selectedVerses,
76
+ actionsShown: actionsShown ?? this.actionsShown,
77
+ highlightsShown: highlightsShown ?? this.highlightsShown,
78
+ highlights: highlights ?? this.highlights,
79
+ );
80
+ }
81
+
82
+ Map<String, dynamic> toJson() => {
83
+ "firstOpen": firstOpen,
84
+ "languageCode": languageCode,
85
+ "bibleName": bibleName,
86
+ "engTitles": engTitles,
87
+ "boldFont": boldFont,
88
+ "darkMode": darkMode,
89
+ "textScale": textScale,
90
+ "savedBook": savedBook,
91
+ "savedChapter": savedChapter,
92
+ "highlights": highlights,
93
+ };
94
+
95
+ factory AppState.fromJson(Map<String, dynamic> json) => AppState(
96
+ firstOpen: json["firstOpen"] as bool? ?? true,
97
+ languageCode: json["languageCode"] as String? ?? "en",
98
+ bibleName: json["bibleName"] as String? ?? "English",
99
+ engTitles: json["engTitles"] as bool? ?? false,
100
+ boldFont: json["boldFont"] as bool? ?? false,
101
+ darkMode: json["darkMode"] as bool? ?? false,
102
+ textScale: (json["textScale"] as num?)?.toDouble() ?? 0.0,
103
+ savedBook: json["savedBook"] as int? ?? 0,
104
+ savedChapter: json["savedChapter"] as int? ?? 0,
105
+ highlights: (json["highlights"] as Map<String, dynamic>?)
106
+ ?.map((k, v) => MapEntry(k, v as int)) ??
107
+ const {},
108
+ );
109
+
110
+ Color? getHighlight(Verse v) {
111
+ final key = "${v.book}:${v.chapter}:${v.index}";
112
+ final index = highlights[key];
113
+ if (index == null) return null;
114
+ return darkMode ? darkHighlights[index] : lightHighlights[index];
115
+ }
116
+
117
+ bool isVerseSelected(Verse v) {
118
+ return selectedVerses.any(
119
+ (el) =>
120
+ el.book == v.book && el.chapter == v.chapter && el.index == v.index,
121
+ );
122
+ }
123
+
124
+ TextStyle getHighlightStyle(BuildContext context, Verse v, bool heading) {
125
+ final isSelected = selectedVerses.any(
126
+ (el) =>
127
+ el.book == v.book && el.chapter == v.chapter && el.index == v.index,
128
+ );
129
+
130
+ if (!heading && isSelected) {
131
+ return TextStyle(
132
+ backgroundColor: darkMode ? Colors.grey.shade800 : Colors.grey.shade200,
133
+ );
134
+ }
135
+ final highlight = getHighlight(v);
136
+ if (darkMode) {
137
+ return TextStyle(
138
+ backgroundColor: highlight?.withValues(alpha: 0.7),
139
+ color: highlight != null
140
+ ? Colors.white
141
+ : Theme.of(context).colorScheme.onSurface,
142
+ );
143
+ }
144
+ return TextStyle(
145
+ backgroundColor: highlight ?? Theme.of(context).colorScheme.surface,
146
+ );
147
+ }
148
+
149
+ Future<void> onPlay(BuildContext context, Bible bible) async {
150
+ final versesToPlay = List<Verse>.from(store.state.selectedVerses);
151
+ if (store.state.isPlaying) {
152
+ await player.pause();
153
+ store.dispatch(SetPlayingAction(false));
154
+ } else {
155
+ store.dispatch(SetPlayingAction(true));
156
+ for (final v in versesToPlay) {
157
+ final pathname = "${bible.name}_${v.book}_${v.chapter}_${v.index}";
158
+ try {
159
+ final data =
160
+ await convertText(context.currentLang.audioVoice, v.text);
161
+ await player.setAudioSource(BufferAudioSource(data));
162
+ await player.play();
163
+ await player.stop();
164
+ } catch (err) {
165
+ log(
166
+ "Could not play audio",
167
+ name: "play",
168
+ error: (err.toString(), pathname),
169
+ );
170
+ recordError((err.toString(), pathname).toString(), null);
171
+ if (context.mounted) {
172
+ showError(context, context.l.audioError);
173
+ }
174
+ return;
175
+ } finally {
176
+ await player.pause();
177
+ store.dispatch(SetPlayingAction(false));
178
+ }
179
+ }
180
+ }
181
+ }
182
+ }
lib/store/buffer_audio_source.dart ADDED
@@ -0,0 +1,25 @@
1
+ import "package:flutter/services.dart";
2
+ import "package:just_audio/just_audio.dart";
3
+
4
+ class BufferAudioSource extends StreamAudioSource {
5
+ final Uint8List _buffer;
6
+
7
+ BufferAudioSource(this._buffer);
8
+
9
+ @override
10
+ Future<StreamAudioResponse> request([int? start, int? end]) {
11
+ start = start ?? 0;
12
+ end = end ?? _buffer.length;
13
+
14
+ return Future.value(
15
+ StreamAudioResponse(
16
+ sourceLength: _buffer.length,
17
+ contentLength: end - start,
18
+ offset: start,
19
+ contentType: "audio/mpeg",
20
+ stream:
21
+ Stream.value(List<int>.from(_buffer.skip(start).take(end - start))),
22
+ ),
23
+ );
24
+ }
25
+ }
lib/store/state.dart DELETED
@@ -1,307 +0,0 @@
1
- import "dart:developer";
2
- import "package:atoms_state/atoms_state.dart";
3
- import "package:flutter/material.dart";
4
- import "package:flutter/services.dart";
5
- import "package:get_storage/get_storage.dart";
6
- import "package:just_audio/just_audio.dart";
7
- import "package:only_bible_app/dialog.dart";
8
- import "package:only_bible_app/models.dart";
9
- import "package:only_bible_app/store/storage.dart";
10
- import "package:only_bible_app/theme.dart";
11
- import "package:only_bible_app/utils.dart";
12
- import "package:only_bible_app/navigation.dart";
13
- import "package:only_bible_app/store/actions.dart";
14
- import "package:path_provider/path_provider.dart";
15
-
16
- final box = GetStorage("only-bible-app-prefs");
17
- final player = AudioPlayer();
18
- final noteTextController = TextEditingController();
19
- final storage = FileStorage(box: box);
20
-
21
- Future<void> initState() async {
22
- await box.initStorage;
23
- }
24
-
25
- final bibleAtom = AsyncAtom(
26
- callback: loadBible,
27
- );
28
-
29
- final firstOpenAtom = Atom(
30
- storage: storage,
31
- key: "firstOpen",
32
- initialState: true,
33
- reducer: (state, action) {
34
- if (action is FirstOpenDone) {
35
- return false;
36
- }
37
- return state;
38
- },
39
- );
40
-
41
- final languageCodeAtom = Atom(
42
- storage: storage,
43
- key: "languageCode",
44
- initialState: "en",
45
- reducer: (state, action) {
46
- if (action is UpdateBible) {
47
- return action.code;
48
- }
49
- return state;
50
- },
51
- );
52
-
53
- final bibleNameAtom = Atom(
54
- storage: storage,
55
- key: "bibleName",
56
- initialState: "English",
57
- reducer: (state, action) {
58
- if (action is UpdateBible) {
59
- return action.name;
60
- }
61
- return state;
62
- },
63
- );
64
-
65
- final engTitlesAtom = Atom(
66
- storage: storage,
67
- key: "engTitles",
68
- initialState: false,
69
- reducer: (state, action) {
70
- if (action is ToggleEngTitles) {
71
- return !state;
72
- }
73
- return state;
74
- },
75
- );
76
-
77
- final boldFontAtom = Atom(
78
- storage: storage,
79
- key: "boldFont",
80
- initialState: false,
81
- reducer: (state, action) {
82
- if (action is ToggleBoldFont) {
83
- return !state;
84
- }
85
- return state;
86
- },
87
- );
88
-
89
- final darkModeAtom = Atom(
90
- storage: storage,
91
- key: "darkMode",
92
- initialState: false,
93
- reducer: (state, action) {
94
- if (action is ToggleDarkMode) {
95
- updateStatusBar(!state);
96
- return !state;
97
- }
98
- return state;
99
- },
100
- );
101
-
102
- final textScaleAtom = Atom(
103
- storage: storage,
104
- key: "textScale",
105
- initialState: 0.0,
106
- reducer: (state, action) {
107
- if (action is UpdateTextScale) {
108
- return state += action.value;
109
- }
110
- return state;
111
- },
112
- );
113
-
114
- final savedBookAtom = Atom(
115
- storage: storage,
116
- key: "savedBook",
117
- initialState: 0,
118
- reducer: (state, action) {
119
- if (action is UpdateChapter) {
120
- return action.book;
121
- }
122
- return state;
123
- },
124
- );
125
-
126
- final savedChapterAtom = Atom(
127
- storage: storage,
128
- key: "savedChapter",
129
- initialState: 0,
130
- reducer: (state, action) {
131
- if (action is UpdateChapter) {
132
- return action.chapter;
133
- }
134
- return state;
135
- },
136
- );
137
-
138
- final isPlaying = Atom(
139
- key: "isPlaying",
140
- initialState: false,
141
- reducer: (state, action) {
142
- if (action is SetPlaying) {
143
- return action.value;
144
- }
145
- return state;
146
- },
147
- );
148
-
149
- final selectedVersesAtom = Atom<List<Verse>, AppAction>(
150
- key: "selectedVerses",
151
- initialState: [],
152
- reducer: (state, action) {
153
- if (action is SelectVerse) {
154
- if (isVerseSelected(action.verse)) {
155
- return (state as List<Verse>)
156
- .removeBy((it) => it.index == action.verse.index)
157
- .toList();
158
- } else {
159
- return (state as List<Verse>).addBy(action.verse).toList();
160
- }
161
- }
162
- if (action is ClearSelectedVerses) {
163
- return List<Verse>.from([]);
164
- }
165
- return state;
166
- },
167
- );
168
-
169
- Color? getHighlight(Verse v) {
170
- final key = "${v.book}:${v.chapter}:${v.index}:highlight";
171
- if (box.hasData(key)) {
172
- // box.remove(key);
173
- // print(box.read(key));
174
- final index = box.read<int>(key);
175
- if (index == null) {
176
- return null;
177
- }
178
- return darkModeAtom.value ? darkHighlights[index] : lightHighlights[index];
179
- }
180
- return null;
181
- }
182
-
183
- void setHighlight(BuildContext context, int index) {
184
- for (final v in selectedVersesAtom.value) {
185
- box.write("${v.book}:${v.chapter}:${v.index}:highlight", index);
186
- }
187
- box.save();
188
- hideActions(context);
189
- }
190
-
191
- void removeHighlight(BuildContext context) {
192
- for (final v in selectedVersesAtom.value) {
193
- box.remove("${v.book}:${v.chapter}:${v.index}:highlight");
194
- }
195
- box.save();
196
- hideActions(context);
197
- }
198
-
199
- bool isVerseSelected(Verse v) {
200
- return selectedVersesAtom.value.any((el) =>
201
- el.book == v.book && el.chapter == v.chapter && el.index == v.index,);
202
- }
203
-
204
- bool watchVerseSelected(BuildContext context, Verse v) {
205
- return selectedVersesAtom.watch(context).any((el) =>
206
- el.book == v.book && el.chapter == v.chapter && el.index == v.index,);
207
- }
208
-
209
- void onVerseSelected(BuildContext context, Bible bible, Verse v) {
210
- dispatch(SelectVerse(v));
211
- if (selectedVersesAtom.value.isNotEmpty) {
212
- showActions(context, bible);
213
- }
214
- if (selectedVersesAtom.value.isEmpty) {
215
- hideActions(context);
216
- }
217
- }
218
-
219
- TextStyle getHighlightStyle(BuildContext context, Verse v, bool heading) {
220
- if (!heading && watchVerseSelected(context, v)) {
221
- return TextStyle(
222
- backgroundColor:
223
- darkModeAtom.value ? Colors.grey.shade800 : Colors.grey.shade200,
224
- );
225
- }
226
- if (darkModeAtom.watch(context)) {
227
- // return TextStyle(
228
- // color: getHighlight(v) ?? context.theme.colorScheme.onBackground,
229
- // );
230
- return TextStyle(
231
- backgroundColor: getHighlight(v)?.withOpacity(0.7),
232
- color: getHighlight(v) != null
233
- ? Colors.white
234
- : context.theme.colorScheme.onSurface,
235
- );
236
- }
237
- return TextStyle(
238
- backgroundColor: getHighlight(v) ?? context.theme.colorScheme.surface,
239
- );
240
- }
241
-
242
- void clearEvents(BuildContext context) {
243
- if (isPlaying.value) {
244
- pause();
245
- }
246
- hideActions(context);
247
- }
248
-
249
- Future<void> pause() async {
250
- await player.pause();
251
- dispatch(const SetPlaying(false));
252
- }
253
-
254
- class BufferAudioSource extends StreamAudioSource {
255
- final Uint8List _buffer;
256
-
257
- BufferAudioSource(this._buffer);
258
-
259
- @override
260
- Future<StreamAudioResponse> request([int? start, int? end]) {
261
- start = start ?? 0;
262
- end = end ?? _buffer.length;
263
-
264
- return Future.value(
265
- StreamAudioResponse(
266
- sourceLength: _buffer.length,
267
- contentLength: end - start,
268
- offset: start,
269
- contentType: "audio/mpeg",
270
- stream:
271
- Stream.value(List<int>.from(_buffer.skip(start).take(end - start))),
272
- ),
273
- );
274
- }
275
- }
276
-
277
- Future<void> onPlay(BuildContext context, Bible bible) async {
278
- final versesToPlay = List<Verse>.from(selectedVersesAtom.value);
279
- if (isPlaying.value) {
280
- pause();
281
- } else {
282
- dispatch(const SetPlaying(true));
283
- for (final v in versesToPlay) {
284
- final directory = await getTemporaryDirectory();
285
- final pathname = "${bible.name}_${v.book}_${v.chapter}_${v.index}";
286
- try {
287
- final data = await convertText(context.currentLang.audioVoice, v.text);
288
- await player.setAudioSource(BufferAudioSource(data));
289
- await player.play();
290
- await player.stop();
291
- } catch (err) {
292
- log(
293
- "Could not play audio",
294
- name: "play",
295
- error: (err.toString(), pathname),
296
- );
297
- recordError((err.toString(), pathname).toString(), null);
298
- if (context.mounted) {
299
- showError(context, context.l.audioError);
300
- }
301
- return;
302
- } finally {
303
- pause();
304
- }
305
- }
306
- }
307
- }
lib/store/storage.dart DELETED
@@ -1,25 +0,0 @@
1
- import "package:atoms_state/atoms_state.dart";
2
- import "package:get_storage/get_storage.dart";
3
-
4
- class FileStorage<T> implements Storage {
5
- GetStorage box;
6
-
7
- FileStorage({required this.box});
8
-
9
- @override
10
- T? get<T>(String key) {
11
- return box.read(key);
12
- }
13
-
14
- @override
15
- Future<void> delete(String key) async {
16
- await box.remove(key);
17
- await box.save();
18
- }
19
-
20
- @override
21
- Future<void> set(String key, value) async {
22
- await box.write(key, value);
23
- await box.save();
24
- }
25
- }
lib/utils.dart CHANGED
@@ -1,10 +1,12 @@
1
1
  import "dart:convert";
2
+ import "package:async_redux/async_redux.dart";
2
3
  import "package:http/http.dart" as http;
3
4
  import "package:flutter/services.dart";
4
5
  import "package:only_bible_app/dialog.dart";
5
6
  import "package:only_bible_app/env.dart";
6
7
  import "package:only_bible_app/models.dart";
7
- import "package:only_bible_app/store/state.dart";
8
+ import "package:only_bible_app/store/app_state.dart";
9
+
8
10
  import "package:package_info_plus/package_info_plus.dart";
9
11
  import "package:url_launcher/url_launcher.dart";
10
12
  import "package:flutter/foundation.dart"
@@ -41,13 +43,14 @@ extension AsyncSnapshotUtils<E> on AsyncSnapshot<E> {
41
43
  extension AppContext on BuildContext {
42
44
  ThemeData get theme => Theme.of(this);
43
45
 
44
- AppLocalizations get l =>
46
+ AppState get state => StoreProvider.state<AppState>(this);
47
+
45
- engTitlesAtom.watch(this) && languageCodeAtom.watch(this) != "en"
48
+ AppLocalizations get l => state.engTitles && state.languageCode != "en"
46
- ? lookupAppLocalizations(const Locale("en"))
49
+ ? lookupAppLocalizations(const Locale("en"))
47
- : AppLocalizations.of(this)!;
50
+ : AppLocalizations.of(this)!;
48
51
 
49
52
  AppLocalizations get currentLang => supportedLocalizations
50
- .firstWhere((el) => el.languageCode == languageCodeAtom.value);
53
+ .firstWhere((el) => el.languageCode == state.languageCode);
51
54
 
52
55
  double get actionsHeight {
53
56
  if (isIOS()) {
lib/widgets/verses_view.dart CHANGED
@@ -3,7 +3,9 @@ import "package:flutter/material.dart";
3
3
  import "package:flutter_swipe_detector/flutter_swipe_detector.dart";
4
4
  import "package:only_bible_app/models.dart";
5
5
  import "package:only_bible_app/navigation.dart";
6
+ import "package:only_bible_app/app.dart";
6
- import "package:only_bible_app/store/state.dart";
7
+ import "package:only_bible_app/store/actions.dart";
8
+ import "package:only_bible_app/utils.dart";
7
9
 
8
10
  class VersesView extends StatelessWidget {
9
11
  final Bible bible;
@@ -15,94 +17,109 @@ class VersesView extends StatelessWidget {
15
17
  Widget build(BuildContext context) {
16
18
  final textStyle = DefaultTextStyle.of(context).style;
17
19
  return SwipeDetector(
18
- onSwipeLeft: (offset) {
20
+ onSwipeLeft: (offset) {
19
- nextChapter(context, bible, chapter.book, chapter.index);
21
+ nextChapter(context, bible, chapter.book, chapter.index);
20
- },
22
+ },
21
- onSwipeRight: (offset) {
23
+ onSwipeRight: (offset) {
22
- previousChapter(context, bible, chapter.book, chapter.index);
24
+ previousChapter(context, bible, chapter.book, chapter.index);
23
- },
25
+ },
24
- child: Column(
26
+ child: Column(
25
- children: [
27
+ children: [
26
- Expanded(
28
+ Expanded(
27
- child: SingleChildScrollView(
29
+ child: SingleChildScrollView(
28
- physics: const BouncingScrollPhysics(),
30
+ physics: const BouncingScrollPhysics(),
29
- child: Padding(
31
+ child: Padding(
30
- padding: const EdgeInsets.only(left: 20, right: 20),
32
+ padding: const EdgeInsets.only(left: 20, right: 20),
31
- child: Column(
33
+ child: Column(
32
- mainAxisAlignment: MainAxisAlignment.start,
34
+ mainAxisAlignment: MainAxisAlignment.start,
33
- crossAxisAlignment: CrossAxisAlignment.start,
35
+ crossAxisAlignment: CrossAxisAlignment.start,
34
- children: [
36
+ children: [
35
- // const Padding(
37
+ // const Padding(
36
- // padding: EdgeInsets.only(top: 0),
38
+ // padding: EdgeInsets.only(top: 0),
37
- // ),
39
+ // ),
38
- Align(
40
+ Align(
39
- alignment: Alignment.centerLeft,
41
+ alignment: Alignment.centerLeft,
40
- child: Text.rich(
42
+ child: Text.rich(
41
- textScaler: TextScaler.linear(
43
+ textScaler: TextScaler.linear(
42
- 1.1 + textScaleAtom.watch(context) / 2,),
44
+ 1.1 + context.state.textScale / 2,
45
+ ),
43
- textAlign: TextAlign.left,
46
+ textAlign: TextAlign.left,
44
- TextSpan(
47
+ TextSpan(
45
- style: boldFontAtom.watch(context)
48
+ style: context.state.boldFont
46
- ? textStyle.copyWith(
49
+ ? textStyle.copyWith(
47
- fontWeight: FontWeight.w500,
50
+ fontWeight: FontWeight.w500,
48
- )
51
+ )
49
- : textStyle,
52
+ : textStyle,
50
- children: chapter.verses
53
+ children: chapter.verses
51
- .map(
54
+ .map(
52
- (v) => [
55
+ (v) => [
53
- if (v.heading != "")
56
+ if (v.heading != "")
54
- TextSpan(
55
- text:
56
- "${v.heading.replaceAll("<br>", "\n")}\n",
57
- style:
58
- getHighlightStyle(context, v, true)
59
- .copyWith(
60
- fontSize: 18,
61
- fontWeight: FontWeight.w600,
62
- height: 2,
63
- ),
64
- ),
65
- WidgetSpan(
66
- child: Transform.translate(
67
- offset: const Offset(0, -2),
68
- child: Text("${v.index + 1} ",
69
- style: Theme.of(context)
70
- .textTheme
71
- .labelMedium,),
72
- ),
73
- ),
74
57
  TextSpan(
75
- text: "${v.text}\n",
76
- style:
58
+ text:
59
+ "${v.heading.replaceAll("<br>", "\n")}\n",
60
+ style: context.state
77
- getHighlightStyle(context, v, false),
61
+ .getHighlightStyle(context, v, true)
78
- recognizer: TapGestureRecognizer()
62
+ .copyWith(
79
- ..onTap = () {
63
+ fontSize: 18,
80
- onVerseSelected(context, bible, v);
64
+ fontWeight: FontWeight.w600,
65
+ height: 2,
81
- },
66
+ ),
82
67
  ),
83
- const WidgetSpan(
68
+ WidgetSpan(
69
+ child: Transform.translate(
70
+ offset: const Offset(0, -2),
84
- child: Padding(
71
+ child: Text(
85
- padding: EdgeInsets.only(
72
+ "${v.index + 1} ",
86
- top: 16, bottom: 16,),
73
+ style: Theme.of(context)
74
+ .textTheme
75
+ .labelMedium,
87
76
  ),
88
77
  ),
78
+ ),
79
+ TextSpan(
80
+ text: "${v.text}\n",
81
+ style: context.state
82
+ .getHighlightStyle(context, v, false),
83
+ recognizer: TapGestureRecognizer()
84
+ ..onTap = () {
85
+ store.dispatch(SelectVerseAction(v));
86
+ if (store
87
+ .state.selectedVerses.isNotEmpty) {
88
+ showActions(context, bible);
89
+ }
90
+ if (store
91
+ .state.selectedVerses.isEmpty) {
92
+ hideActions(context);
93
+ }
94
+ },
95
+ ),
96
+ const WidgetSpan(
97
+ child: Padding(
98
+ padding: EdgeInsets.only(
99
+ top: 16,
100
+ bottom: 16,
101
+ ),
102
+ ),
103
+ ),
89
- ],
104
+ ],
90
- )
105
+ )
91
- .expand((element) => element)
106
+ .expand((element) => element)
92
- .toList(),
107
+ .toList(),
93
- ),
94
108
  ),
95
109
  ),
110
+ ),
96
- Padding(
111
+ Padding(
97
- padding: EdgeInsets.only(
112
+ padding: EdgeInsets.only(
98
- bottom: actionsShownAtom.watch(context) ? 120 : 0,),
113
+ bottom: context.state.actionsShown ? 120 : 0,
99
114
  ),
115
+ ),
100
- ],
116
+ ],
101
- ),
102
117
  ),
103
118
  ),
104
119
  ),
120
+ ),
105
- ],
121
+ ],
106
- ),);
122
+ ),
123
+ );
107
124
  }
108
125
  }
macos/Flutter/GeneratedPluginRegistrant.swift CHANGED
@@ -7,6 +7,7 @@ import Foundation
7
7
 
8
8
  import app_review
9
9
  import audio_session
10
+ import connectivity_plus
10
11
  import just_audio
11
12
  import package_info_plus
12
13
  import share_plus
@@ -17,6 +18,7 @@ import webview_flutter_wkwebview
17
18
  func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
18
19
  AppReviewPlugin.register(with: registry.registrar(forPlugin: "AppReviewPlugin"))
19
20
  AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
21
+ ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
20
22
  JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin"))
21
23
  FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
22
24
  SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
pubspec.lock CHANGED
@@ -57,14 +57,22 @@ packages:
57
57
  url: "https://pub.dev"
58
58
  source: hosted
59
59
  version: "2.13.0"
60
- atoms_state:
60
+ async_redux:
61
61
  dependency: "direct main"
62
62
  description:
63
- name: atoms_state
63
+ name: async_redux
64
- sha256: "25ffb62bf5f0bdf4ee437e32d450fa55dc5e6ceadda6056b1b7d13f1549cff9c"
64
+ sha256: "3f214bcc112a3033a137f47307de30d793a66ff4ee033c1fa6232bbd2e575df0"
65
65
  url: "https://pub.dev"
66
66
  source: hosted
67
+ version: "27.1.1"
68
+ async_redux_core:
69
+ dependency: transitive
70
+ description:
71
+ name: async_redux_core
72
+ sha256: "7c1075bc14304ae23352ccea5fe16515720a6d4d5a8f6d0e65ae63dc4b243c27"
73
+ url: "https://pub.dev"
74
+ source: hosted
67
- version: "0.0.2"
75
+ version: "1.5.0"
68
76
  audio_session:
69
77
  dependency: transitive
70
78
  description:
@@ -201,6 +209,22 @@ packages:
201
209
  url: "https://pub.dev"
202
210
  source: hosted
203
211
  version: "3.0.0"
212
+ connectivity_plus:
213
+ dependency: transitive
214
+ description:
215
+ name: connectivity_plus
216
+ sha256: "33bae12a398f841c6cda09d1064212957265869104c478e5ad51e2fb26c3973c"
217
+ url: "https://pub.dev"
218
+ source: hosted
219
+ version: "7.0.0"
220
+ connectivity_plus_platform_interface:
221
+ dependency: transitive
222
+ description:
223
+ name: connectivity_plus_platform_interface
224
+ sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204"
225
+ url: "https://pub.dev"
226
+ source: hosted
227
+ version: "2.0.1"
204
228
  convert:
205
229
  dependency: transitive
206
230
  description:
@@ -265,6 +289,14 @@ packages:
265
289
  url: "https://pub.dev"
266
290
  source: hosted
267
291
  version: "1.2.0"
292
+ dbus:
293
+ dependency: transitive
294
+ description:
295
+ name: dbus
296
+ sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270
297
+ url: "https://pub.dev"
298
+ source: hosted
299
+ version: "0.7.12"
268
300
  equatable:
269
301
  dependency: transitive
270
302
  description:
@@ -281,6 +313,14 @@ packages:
281
313
  url: "https://pub.dev"
282
314
  source: hosted
283
315
  version: "1.3.3"
316
+ fast_immutable_collections:
317
+ dependency: transitive
318
+ description:
319
+ name: fast_immutable_collections
320
+ sha256: "19f70498af299cbce5ff919dbbecd5abfd9d0c28139004f68d3810ce23dedfb3"
321
+ url: "https://pub.dev"
322
+ source: hosted
323
+ version: "11.1.0"
284
324
  ffi:
285
325
  dependency: transitive
286
326
  description:
@@ -399,22 +439,6 @@ packages:
399
439
  description: flutter
400
440
  source: sdk
401
441
  version: "0.0.0"
402
- get:
403
- dependency: transitive
404
- description:
405
- name: get
406
- sha256: "5ed34a7925b85336e15d472cc4cfe7d9ebf4ab8e8b9f688585bf6b50f4c3d79a"
407
- url: "https://pub.dev"
408
- source: hosted
409
- version: "4.7.3"
410
- get_storage:
411
- dependency: "direct main"
412
- description:
413
- name: get_storage
414
- sha256: "39db1fffe779d0c22b3a744376e86febe4ade43bf65e06eab5af707dc84185a2"
415
- url: "https://pub.dev"
416
- source: hosted
417
- version: "2.1.1"
418
442
  glob:
419
443
  dependency: transitive
420
444
  description:
@@ -487,6 +511,14 @@ packages:
487
511
  url: "https://pub.dev"
488
512
  source: hosted
489
513
  version: "4.1.2"
514
+ i18n_extension_core:
515
+ dependency: transitive
516
+ description:
517
+ name: i18n_extension_core
518
+ sha256: "5d17d355b95882e803f89c97cad610ab7967a798f8e519563d274f7888bc968e"
519
+ url: "https://pub.dev"
520
+ source: hosted
521
+ version: "5.0.2"
490
522
  image:
491
523
  dependency: transitive
492
524
  description:
@@ -636,6 +668,14 @@ packages:
636
668
  url: "https://pub.dev"
637
669
  source: hosted
638
670
  version: "0.17.6"
671
+ nm:
672
+ dependency: transitive
673
+ description:
674
+ name: nm
675
+ sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
676
+ url: "https://pub.dev"
677
+ source: hosted
678
+ version: "0.5.0"
639
679
  node_preamble:
640
680
  dependency: transitive
641
681
  description:
@@ -812,6 +852,14 @@ packages:
812
852
  url: "https://pub.dev"
813
853
  source: hosted
814
854
  version: "0.28.0"
855
+ serverpod_serialization:
856
+ dependency: transitive
857
+ description:
858
+ name: serverpod_serialization
859
+ sha256: "6b087d9e6075dec0973c31220b0ba09f67d31d79cb691f689a704e4b7f8343a3"
860
+ url: "https://pub.dev"
861
+ source: hosted
862
+ version: "3.4.4"
815
863
  settings_ui:
816
864
  dependency: "direct main"
817
865
  description:
@@ -953,6 +1001,14 @@ packages:
953
1001
  url: "https://pub.dev"
954
1002
  source: hosted
955
1003
  version: "1.10.2"
1004
+ sprintf:
1005
+ dependency: transitive
1006
+ description:
1007
+ name: sprintf
1008
+ sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
1009
+ url: "https://pub.dev"
1010
+ source: hosted
1011
+ version: "7.0.0"
956
1012
  stack_trace:
957
1013
  dependency: transitive
958
1014
  description:
@@ -1169,6 +1225,14 @@ packages:
1169
1225
  url: "https://pub.dev"
1170
1226
  source: hosted
1171
1227
  version: "1.2.1"
1228
+ weak_map:
1229
+ dependency: transitive
1230
+ description:
1231
+ name: weak_map
1232
+ sha256: "5f8e5d5ce57dc624db5fae814dd689ccae1f17f92b426e52f0a7cbe7f6f4ab97"
1233
+ url: "https://pub.dev"
1234
+ source: hosted
1235
+ version: "4.0.1"
1172
1236
  web:
1173
1237
  dependency: transitive
1174
1238
  description:
pubspec.yaml CHANGED
@@ -21,16 +21,15 @@ dependencies:
21
21
  flutter_swipe_detector: ^2.0.0
22
22
  cupertino_icons: ^1.0.8
23
23
  settings_ui: ^2.0.2
24
- get_storage: ^2.1.1
25
24
  share_plus: ^12.0.1
26
25
  url_launcher: ^6.3.2
27
26
  package_info_plus: ^9.0.0
28
27
  flutter_azure_tts: ^1.0.0
29
28
  http: ^1.6.0
30
- atoms_state: ^0.0.2
31
29
  webview_flutter: ^4.13.1
32
30
  app_review: ^3.0.0
33
31
  go_router: ^17.1.0
32
+ async_redux: ^27.1.1
34
33
 
35
34
  dev_dependencies:
36
35
  flutter_test:
windows/flutter/generated_plugin_registrant.cc CHANGED
@@ -6,10 +6,13 @@
6
6
 
7
7
  #include "generated_plugin_registrant.h"
8
8
 
9
+ #include <connectivity_plus/connectivity_plus_windows_plugin.h>
9
10
  #include <share_plus/share_plus_windows_plugin_c_api.h>
10
11
  #include <url_launcher_windows/url_launcher_windows.h>
11
12
 
12
13
  void RegisterPlugins(flutter::PluginRegistry* registry) {
14
+ ConnectivityPlusWindowsPluginRegisterWithRegistrar(
15
+ registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
13
16
  SharePlusWindowsPluginCApiRegisterWithRegistrar(
14
17
  registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
15
18
  UrlLauncherWindowsRegisterWithRegistrar(
windows/flutter/generated_plugins.cmake CHANGED
@@ -3,6 +3,7 @@
3
3
  #
4
4
 
5
5
  list(APPEND FLUTTER_PLUGIN_LIST
6
+ connectivity_plus
6
7
  share_plus
7
8
  url_launcher_windows
8
9
  )