only-bible-app v3.7.2+24
git clone https://git.pyrossh.dev/only-bible-app
The only bible app you will ever need. No ads. No in-app purchases. No distractions.
0228ae8
— Peter John
2026-08-08T15:28:12+05:30
major improvements please
- Makefile +19 -1
- android/fastlane/Fastfile +10 -4
- ios/Flutter/Debug.xcconfig +0 -1
- ios/Flutter/Release.xcconfig +0 -1
- ios/Podfile +0 -43
- ios/Podfile.lock +0 -22
- ios/Runner.xcodeproj/project.pbxproj +17 -110
- ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +0 -59
- ios/Runner.xcworkspace/contents.xcworkspacedata +0 -3
- ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved +0 -59
- ios/Runner/Info.plist +1 -5
- ios/fastlane/Fastfile +7 -3
- lib/app.dart +1 -1
- lib/dialog.dart +1 -1
- lib/dialogs.dart +66 -0
- lib/error_reporting.dart +147 -0
- lib/main.dart +1 -1
- lib/store/actions_backup.dart +49 -29
- lib/store/actions_state.dart +26 -29
- lib/theme.dart +343 -0
- lib/utils.dart +44 -21
- macos/Flutter/Flutter-Debug.xcconfig +0 -1
- macos/Flutter/Flutter-Release.xcconfig +0 -1
- macos/Flutter/GeneratedPluginRegistrant.swift +4 -6
- macos/Podfile +0 -42
- macos/Podfile.lock +0 -22
- macos/Runner.xcodeproj/project.pbxproj +3 -107
- macos/Runner.xcworkspace/contents.xcworkspacedata +0 -3
- macos/Runner/Info.plist +0 -4
- macos/fastlane/Fastfile +4 -3
- macos/fastlane/report.xml +41 -6
- pubspec.lock +108 -155
- pubspec.yaml +3 -5
- readme.md +7 -49
- test/app_logic_test.dart +288 -89
- test/screenshot_test.dart +1 -1
Makefile
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.PHONY: run test analyze macos ios android android-device list-devices setup
|
|
1
|
+
.PHONY: run test analyze macos ios android android-device list-devices setup release release-android release-ios release-macos
|
|
2
2
|
|
|
3
3
|
setup:
|
|
4
4
|
brew install fastlane
|
|
@@ -36,3 +36,21 @@ list-devices:
|
|
|
36
36
|
@echo ""
|
|
37
37
|
@echo "--- Android Emulators ---"
|
|
38
38
|
emulator -list-avds 2>/dev/null || echo "No AVDs found (set ANDROID_HOME)"
|
|
39
|
+
|
|
40
|
+
# Builds, uploads and publishes to the Play Store, App Store and Mac App Store
|
|
41
|
+
# in one go. Screenshots are regenerated once here instead of once per
|
|
42
|
+
# platform (skip_screenshots:true tells each lane to reuse them).
|
|
43
|
+
release:
|
|
44
|
+
flutter test test/screenshot_test.dart --update-goldens
|
|
45
|
+
$(MAKE) release-android
|
|
46
|
+
$(MAKE) release-ios
|
|
47
|
+
$(MAKE) release-macos
|
|
48
|
+
|
|
49
|
+
release-android:
|
|
50
|
+
cd android && fastlane deploy skip_screenshots:true
|
|
51
|
+
|
|
52
|
+
release-ios:
|
|
53
|
+
cd ios && fastlane release skip_screenshots:true
|
|
54
|
+
|
|
55
|
+
release-macos:
|
|
56
|
+
cd macos && fastlane release skip_screenshots:true
|
android/fastlane/Fastfile
CHANGED
|
@@ -36,10 +36,16 @@ platform :android do
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
desc "Deploy a new version to the Google Play"
|
|
39
|
-
lane :deploy do
|
|
39
|
+
lane :deploy do |options|
|
|
40
|
-
screenshots
|
|
40
|
+
screenshots unless options[:skip_screenshots]
|
|
41
|
-
gradle(task: "clean assembleRelease")
|
|
41
|
+
# Invoking gradle directly (as the old `gradle(task: "clean assembleRelease")`
|
|
42
|
+
# did) skips `flutter pub get`'s plugin-registrant regeneration, which is
|
|
43
|
+
# what excludes dev-only plugins like integration_test from the release
|
|
44
|
+
# classpath. Building through `flutter build` runs that step first, so
|
|
45
|
+
# GeneratedPluginRegistrant.java is correct for a release build. It also
|
|
46
|
+
# produces an .aab (assembleRelease only produces an .apk).
|
|
47
|
+
sh("cd ../.. && flutter build appbundle --release")
|
|
42
|
-
|
|
48
|
+
upload
|
|
43
49
|
end
|
|
44
50
|
|
|
45
51
|
desc "Upload a pre-built AAB to the Google Play"
|
ios/Flutter/Debug.xcconfig
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
|
2
1
|
#include "Generated.xcconfig"
|
ios/Flutter/Release.xcconfig
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
|
2
1
|
#include "Generated.xcconfig"
|
ios/Podfile
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# Uncomment this line to define a global platform for your project
|
|
2
|
-
platform :ios, '13.0'
|
|
3
|
-
|
|
4
|
-
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
|
5
|
-
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
|
6
|
-
|
|
7
|
-
project 'Runner', {
|
|
8
|
-
'Debug' => :debug,
|
|
9
|
-
'Profile' => :release,
|
|
10
|
-
'Release' => :release,
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
def flutter_root
|
|
14
|
-
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
|
15
|
-
unless File.exist?(generated_xcode_build_settings_path)
|
|
16
|
-
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
File.foreach(generated_xcode_build_settings_path) do |line|
|
|
20
|
-
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
|
21
|
-
return matches[1].strip if matches
|
|
22
|
-
end
|
|
23
|
-
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
|
27
|
-
|
|
28
|
-
flutter_ios_podfile_setup
|
|
29
|
-
|
|
30
|
-
target 'Runner' do
|
|
31
|
-
use_frameworks!
|
|
32
|
-
|
|
33
|
-
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
|
34
|
-
target 'RunnerTests' do
|
|
35
|
-
inherit! :search_paths
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
post_install do |installer|
|
|
40
|
-
installer.pods_project.targets.each do |target|
|
|
41
|
-
flutter_additional_ios_build_settings(target)
|
|
42
|
-
end
|
|
43
|
-
end
|
ios/Podfile.lock
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
PODS:
|
|
2
|
-
- Flutter (1.0.0)
|
|
3
|
-
- flutter_soloud (0.0.1):
|
|
4
|
-
- Flutter
|
|
5
|
-
|
|
6
|
-
DEPENDENCIES:
|
|
7
|
-
- Flutter (from `Flutter`)
|
|
8
|
-
- flutter_soloud (from `.symlinks/plugins/flutter_soloud/ios`)
|
|
9
|
-
|
|
10
|
-
EXTERNAL SOURCES:
|
|
11
|
-
Flutter:
|
|
12
|
-
:path: Flutter
|
|
13
|
-
flutter_soloud:
|
|
14
|
-
:path: ".symlinks/plugins/flutter_soloud/ios"
|
|
15
|
-
|
|
16
|
-
SPEC CHECKSUMS:
|
|
17
|
-
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
|
18
|
-
flutter_soloud: 272cc604453b6ca135f274045fb259e7c6426122
|
|
19
|
-
|
|
20
|
-
PODFILE CHECKSUM: 251cb053df7158f337c0712f2ab29f4e0fa474ce
|
|
21
|
-
|
|
22
|
-
COCOAPODS: 1.16.2
|
ios/Runner.xcodeproj/project.pbxproj
CHANGED
|
@@ -9,15 +9,13 @@
|
|
|
9
9
|
/* Begin PBXBuildFile section */
|
|
10
10
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
|
11
11
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
|
12
|
-
391BE44176019420B8A3CC71 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 47321D7A556A4A9BF16431AF /* Pods_RunnerTests.framework */; };
|
|
13
12
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
|
14
13
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
|
15
14
|
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; };
|
|
15
|
+
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
|
|
16
16
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
|
17
17
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
|
18
18
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
|
19
|
-
B492E2663736AD73EF7204F6 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 547E0FBE28272A54761BA898 /* Pods_Runner.framework */; };
|
|
20
|
-
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
|
|
21
19
|
/* End PBXBuildFile section */
|
|
22
20
|
|
|
23
21
|
/* Begin PBXContainerItemProxy section */
|
|
@@ -49,16 +47,11 @@
|
|
|
49
47
|
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
|
50
48
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
51
49
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
|
52
|
-
43630B2A8F1EB61852D12394 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
|
53
|
-
4420C6B24FA27C901C38F165 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
|
54
|
-
47321D7A556A4A9BF16431AF /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
55
|
-
547E0FBE28272A54761BA898 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
56
|
-
6044DC6CC5B6EC34A4CDDEAB /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
|
57
50
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
|
58
51
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
|
59
52
|
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
|
53
|
+
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
|
|
60
54
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
|
61
|
-
8445FAB3A6845463961F425E /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
|
62
55
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
|
63
56
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
|
64
57
|
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
@@ -66,9 +59,6 @@
|
|
|
66
59
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
|
67
60
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
|
68
61
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
|
69
|
-
B617937D6DA9BB0132F54E43 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
|
70
|
-
D7CDE34E68CE03AC0364CCBE /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
|
71
|
-
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
|
|
72
62
|
/* End PBXFileReference section */
|
|
73
63
|
|
|
74
64
|
/* Begin PBXFrameworksBuildPhase section */
|
|
@@ -76,7 +66,6 @@
|
|
|
76
66
|
isa = PBXFrameworksBuildPhase;
|
|
77
67
|
buildActionMask = 2147483647;
|
|
78
68
|
files = (
|
|
79
|
-
391BE44176019420B8A3CC71 /* Pods_RunnerTests.framework in Frameworks */,
|
|
80
69
|
);
|
|
81
70
|
runOnlyForDeploymentPostprocessing = 0;
|
|
82
71
|
};
|
|
@@ -85,7 +74,6 @@
|
|
|
85
74
|
buildActionMask = 2147483647;
|
|
86
75
|
files = (
|
|
87
76
|
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
|
|
88
|
-
B492E2663736AD73EF7204F6 /* Pods_Runner.framework in Frameworks */,
|
|
89
77
|
);
|
|
90
78
|
runOnlyForDeploymentPostprocessing = 0;
|
|
91
79
|
};
|
|
@@ -103,12 +91,6 @@
|
|
|
103
91
|
818932C489EE28D703604D07 /* Pods */ = {
|
|
104
92
|
isa = PBXGroup;
|
|
105
93
|
children = (
|
|
106
|
-
6044DC6CC5B6EC34A4CDDEAB /* Pods-Runner.debug.xcconfig */,
|
|
107
|
-
B617937D6DA9BB0132F54E43 /* Pods-Runner.release.xcconfig */,
|
|
108
|
-
43630B2A8F1EB61852D12394 /* Pods-Runner.profile.xcconfig */,
|
|
109
|
-
4420C6B24FA27C901C38F165 /* Pods-RunnerTests.debug.xcconfig */,
|
|
110
|
-
D7CDE34E68CE03AC0364CCBE /* Pods-RunnerTests.release.xcconfig */,
|
|
111
|
-
8445FAB3A6845463961F425E /* Pods-RunnerTests.profile.xcconfig */,
|
|
112
94
|
);
|
|
113
95
|
path = Pods;
|
|
114
96
|
sourceTree = "<group>";
|
|
@@ -133,7 +115,6 @@
|
|
|
133
115
|
97C146EF1CF9000F007C117D /* Products */,
|
|
134
116
|
331C8082294A63A400263BE5 /* RunnerTests */,
|
|
135
117
|
818932C489EE28D703604D07 /* Pods */,
|
|
136
|
-
AB430DFF6A3D37D341DBDBAC /* Frameworks */,
|
|
137
118
|
);
|
|
138
119
|
sourceTree = "<group>";
|
|
139
120
|
};
|
|
@@ -162,15 +143,6 @@
|
|
|
162
143
|
path = Runner;
|
|
163
144
|
sourceTree = "<group>";
|
|
164
145
|
};
|
|
165
|
-
AB430DFF6A3D37D341DBDBAC /* Frameworks */ = {
|
|
166
|
-
isa = PBXGroup;
|
|
167
|
-
children = (
|
|
168
|
-
547E0FBE28272A54761BA898 /* Pods_Runner.framework */,
|
|
169
|
-
47321D7A556A4A9BF16431AF /* Pods_RunnerTests.framework */,
|
|
170
|
-
);
|
|
171
|
-
name = Frameworks;
|
|
172
|
-
sourceTree = "<group>";
|
|
173
|
-
};
|
|
174
146
|
/* End PBXGroup section */
|
|
175
147
|
|
|
176
148
|
/* Begin PBXNativeTarget section */
|
|
@@ -178,7 +150,6 @@
|
|
|
178
150
|
isa = PBXNativeTarget;
|
|
179
151
|
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
|
180
152
|
buildPhases = (
|
|
181
|
-
1B99EC6CBC943BDF479A1466 /* [CP] Check Pods Manifest.lock */,
|
|
182
153
|
331C807D294A63A400263BE5 /* Sources */,
|
|
183
154
|
331C807F294A63A400263BE5 /* Resources */,
|
|
184
155
|
13AD50860C8A698628F45191 /* Frameworks */,
|
|
@@ -194,26 +165,24 @@
|
|
|
194
165
|
productType = "com.apple.product-type.bundle.unit-test";
|
|
195
166
|
};
|
|
196
167
|
97C146ED1CF9000F007C117D /* Runner */ = {
|
|
197
|
-
packageProductDependencies = (
|
|
198
|
-
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
|
|
199
|
-
);
|
|
200
168
|
isa = PBXNativeTarget;
|
|
201
169
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
|
202
170
|
buildPhases = (
|
|
203
|
-
5DA2454F7F236B7C3EACFB0C /* [CP] Check Pods Manifest.lock */,
|
|
204
171
|
9740EEB61CF901F6004384FC /* Run Script */,
|
|
205
172
|
97C146EA1CF9000F007C117D /* Sources */,
|
|
206
173
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
|
207
174
|
97C146EC1CF9000F007C117D /* Resources */,
|
|
208
175
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
|
209
176
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
|
210
|
-
C1FC2023EB277B23DF78E0EB /* [CP] Embed Pods Frameworks */,
|
|
211
177
|
);
|
|
212
178
|
buildRules = (
|
|
213
179
|
);
|
|
214
180
|
dependencies = (
|
|
215
181
|
);
|
|
216
182
|
name = Runner;
|
|
183
|
+
packageProductDependencies = (
|
|
184
|
+
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
|
|
185
|
+
);
|
|
217
186
|
productName = Runner;
|
|
218
187
|
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
|
219
188
|
productType = "com.apple.product-type.application";
|
|
@@ -222,9 +191,6 @@
|
|
|
222
191
|
|
|
223
192
|
/* Begin PBXProject section */
|
|
224
193
|
97C146E61CF9000F007C117D /* Project object */ = {
|
|
225
|
-
packageReferences = (
|
|
226
|
-
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
|
|
227
|
-
);
|
|
228
194
|
isa = PBXProject;
|
|
229
195
|
attributes = {
|
|
230
196
|
BuildIndependentTargetsInParallel = YES;
|
|
@@ -250,6 +216,9 @@
|
|
|
250
216
|
Base,
|
|
251
217
|
);
|
|
252
218
|
mainGroup = 97C146E51CF9000F007C117D;
|
|
219
|
+
packageReferences = (
|
|
220
|
+
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */,
|
|
221
|
+
);
|
|
253
222
|
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
|
254
223
|
projectDirPath = "";
|
|
255
224
|
projectRoot = "";
|
|
@@ -282,28 +251,6 @@
|
|
|
282
251
|
/* End PBXResourcesBuildPhase section */
|
|
283
252
|
|
|
284
253
|
/* Begin PBXShellScriptBuildPhase section */
|
|
285
|
-
1B99EC6CBC943BDF479A1466 /* [CP] Check Pods Manifest.lock */ = {
|
|
286
|
-
isa = PBXShellScriptBuildPhase;
|
|
287
|
-
buildActionMask = 2147483647;
|
|
288
|
-
files = (
|
|
289
|
-
);
|
|
290
|
-
inputFileListPaths = (
|
|
291
|
-
);
|
|
292
|
-
inputPaths = (
|
|
293
|
-
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
|
294
|
-
"${PODS_ROOT}/Manifest.lock",
|
|
295
|
-
);
|
|
296
|
-
name = "[CP] Check Pods Manifest.lock";
|
|
297
|
-
outputFileListPaths = (
|
|
298
|
-
);
|
|
299
|
-
outputPaths = (
|
|
300
|
-
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
|
|
301
|
-
);
|
|
302
|
-
runOnlyForDeploymentPostprocessing = 0;
|
|
303
|
-
shellPath = /bin/sh;
|
|
304
|
-
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
|
305
|
-
showEnvVarsInLog = 0;
|
|
306
|
-
};
|
|
307
254
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
|
308
255
|
isa = PBXShellScriptBuildPhase;
|
|
309
256
|
alwaysOutOfDate = 1;
|
|
@@ -320,28 +267,6 @@
|
|
|
320
267
|
shellPath = /bin/sh;
|
|
321
268
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
|
322
269
|
};
|
|
323
|
-
5DA2454F7F236B7C3EACFB0C /* [CP] Check Pods Manifest.lock */ = {
|
|
324
|
-
isa = PBXShellScriptBuildPhase;
|
|
325
|
-
buildActionMask = 2147483647;
|
|
326
|
-
files = (
|
|
327
|
-
);
|
|
328
|
-
inputFileListPaths = (
|
|
329
|
-
);
|
|
330
|
-
inputPaths = (
|
|
331
|
-
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
|
332
|
-
"${PODS_ROOT}/Manifest.lock",
|
|
333
|
-
);
|
|
334
|
-
name = "[CP] Check Pods Manifest.lock";
|
|
335
|
-
outputFileListPaths = (
|
|
336
|
-
);
|
|
337
|
-
outputPaths = (
|
|
338
|
-
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
|
339
|
-
);
|
|
340
|
-
runOnlyForDeploymentPostprocessing = 0;
|
|
341
|
-
shellPath = /bin/sh;
|
|
342
|
-
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
|
343
|
-
showEnvVarsInLog = 0;
|
|
344
|
-
};
|
|
345
270
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
|
346
271
|
isa = PBXShellScriptBuildPhase;
|
|
347
272
|
alwaysOutOfDate = 1;
|
|
@@ -357,23 +282,6 @@
|
|
|
357
282
|
shellPath = /bin/sh;
|
|
358
283
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
|
359
284
|
};
|
|
360
|
-
C1FC2023EB277B23DF78E0EB /* [CP] Embed Pods Frameworks */ = {
|
|
361
|
-
isa = PBXShellScriptBuildPhase;
|
|
362
|
-
buildActionMask = 2147483647;
|
|
363
|
-
files = (
|
|
364
|
-
);
|
|
365
|
-
inputFileListPaths = (
|
|
366
|
-
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
|
367
|
-
);
|
|
368
|
-
name = "[CP] Embed Pods Frameworks";
|
|
369
|
-
outputFileListPaths = (
|
|
370
|
-
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
|
371
|
-
);
|
|
372
|
-
runOnlyForDeploymentPostprocessing = 0;
|
|
373
|
-
shellPath = /bin/sh;
|
|
374
|
-
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
|
375
|
-
showEnvVarsInLog = 0;
|
|
376
|
-
};
|
|
377
285
|
/* End PBXShellScriptBuildPhase section */
|
|
378
286
|
|
|
379
287
|
/* Begin PBXSourcesBuildPhase section */
|
|
@@ -485,7 +393,7 @@
|
|
|
485
393
|
CLANG_ENABLE_MODULES = YES;
|
|
486
394
|
CODE_SIGN_IDENTITY = "Apple Development";
|
|
487
395
|
CODE_SIGN_STYLE = Automatic;
|
|
488
|
-
CURRENT_PROJECT_VERSION =
|
|
396
|
+
CURRENT_PROJECT_VERSION = 8;
|
|
489
397
|
DEVELOPMENT_TEAM = FXH8NUGFYY;
|
|
490
398
|
ENABLE_BITCODE = NO;
|
|
491
399
|
INFOPLIST_FILE = Runner/Info.plist;
|
|
@@ -504,11 +412,10 @@
|
|
|
504
412
|
};
|
|
505
413
|
331C8088294A63A400263BE5 /* Debug */ = {
|
|
506
414
|
isa = XCBuildConfiguration;
|
|
507
|
-
baseConfigurationReference = 4420C6B24FA27C901C38F165 /* Pods-RunnerTests.debug.xcconfig */;
|
|
508
415
|
buildSettings = {
|
|
509
416
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
|
510
417
|
CODE_SIGN_STYLE = Automatic;
|
|
511
|
-
CURRENT_PROJECT_VERSION =
|
|
418
|
+
CURRENT_PROJECT_VERSION = 8;
|
|
512
419
|
GENERATE_INFOPLIST_FILE = YES;
|
|
513
420
|
MARKETING_VERSION = 1.0;
|
|
514
421
|
PRODUCT_BUNDLE_IDENTIFIER = "sh.pyros.only-bible-app.RunnerTests";
|
|
@@ -522,11 +429,10 @@
|
|
|
522
429
|
};
|
|
523
430
|
331C8089294A63A400263BE5 /* Release */ = {
|
|
524
431
|
isa = XCBuildConfiguration;
|
|
525
|
-
baseConfigurationReference = D7CDE34E68CE03AC0364CCBE /* Pods-RunnerTests.release.xcconfig */;
|
|
526
432
|
buildSettings = {
|
|
527
433
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
|
528
434
|
CODE_SIGN_STYLE = Automatic;
|
|
529
|
-
CURRENT_PROJECT_VERSION =
|
|
435
|
+
CURRENT_PROJECT_VERSION = 8;
|
|
530
436
|
GENERATE_INFOPLIST_FILE = YES;
|
|
531
437
|
MARKETING_VERSION = 1.0;
|
|
532
438
|
PRODUCT_BUNDLE_IDENTIFIER = "sh.pyros.only-bible-app.RunnerTests";
|
|
@@ -538,11 +444,10 @@
|
|
|
538
444
|
};
|
|
539
445
|
331C808A294A63A400263BE5 /* Profile */ = {
|
|
540
446
|
isa = XCBuildConfiguration;
|
|
541
|
-
baseConfigurationReference = 8445FAB3A6845463961F425E /* Pods-RunnerTests.profile.xcconfig */;
|
|
542
447
|
buildSettings = {
|
|
543
448
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
|
544
449
|
CODE_SIGN_STYLE = Automatic;
|
|
545
|
-
CURRENT_PROJECT_VERSION =
|
|
450
|
+
CURRENT_PROJECT_VERSION = 8;
|
|
546
451
|
GENERATE_INFOPLIST_FILE = YES;
|
|
547
452
|
MARKETING_VERSION = 1.0;
|
|
548
453
|
PRODUCT_BUNDLE_IDENTIFIER = "sh.pyros.only-bible-app.RunnerTests";
|
|
@@ -671,7 +576,7 @@
|
|
|
671
576
|
CLANG_ENABLE_MODULES = YES;
|
|
672
577
|
CODE_SIGN_IDENTITY = "Apple Development";
|
|
673
578
|
CODE_SIGN_STYLE = Automatic;
|
|
674
|
-
CURRENT_PROJECT_VERSION =
|
|
579
|
+
CURRENT_PROJECT_VERSION = 8;
|
|
675
580
|
DEVELOPMENT_TEAM = FXH8NUGFYY;
|
|
676
581
|
ENABLE_BITCODE = NO;
|
|
677
582
|
INFOPLIST_FILE = Runner/Info.plist;
|
|
@@ -698,7 +603,7 @@
|
|
|
698
603
|
CODE_SIGN_IDENTITY = "Apple Development";
|
|
699
604
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
|
700
605
|
CODE_SIGN_STYLE = Manual;
|
|
701
|
-
CURRENT_PROJECT_VERSION =
|
|
606
|
+
CURRENT_PROJECT_VERSION = 8;
|
|
702
607
|
DEVELOPMENT_TEAM = "";
|
|
703
608
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = FXH8NUGFYY;
|
|
704
609
|
ENABLE_BITCODE = NO;
|
|
@@ -751,12 +656,14 @@
|
|
|
751
656
|
defaultConfigurationName = Release;
|
|
752
657
|
};
|
|
753
658
|
/* End XCConfigurationList section */
|
|
659
|
+
|
|
754
660
|
/* Begin XCLocalSwiftPackageReference section */
|
|
755
|
-
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "
|
|
661
|
+
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = {
|
|
756
662
|
isa = XCLocalSwiftPackageReference;
|
|
757
663
|
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
|
|
758
664
|
};
|
|
759
665
|
/* End XCLocalSwiftPackageReference section */
|
|
666
|
+
|
|
760
667
|
/* Begin XCSwiftPackageProductDependency section */
|
|
761
668
|
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
|
|
762
669
|
isa = XCSwiftPackageProductDependency;
|
ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"pins" : [
|
|
3
|
-
{
|
|
4
|
-
"identity" : "dkcamera",
|
|
5
|
-
"kind" : "remoteSourceControl",
|
|
6
|
-
"location" : "https://github.com/zhangao0086/DKCamera",
|
|
7
|
-
"state" : {
|
|
8
|
-
"branch" : "master",
|
|
9
|
-
"revision" : "5c691d11014b910aff69f960475d70e65d9dcc96"
|
|
10
|
-
}
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
"identity" : "dkimagepickercontroller",
|
|
14
|
-
"kind" : "remoteSourceControl",
|
|
15
|
-
"location" : "https://github.com/zhangao0086/DKImagePickerController",
|
|
16
|
-
"state" : {
|
|
17
|
-
"branch" : "4.3.9",
|
|
18
|
-
"revision" : "0bdfeacefa308545adde07bef86e349186335915"
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
"identity" : "dkphotogallery",
|
|
23
|
-
"kind" : "remoteSourceControl",
|
|
24
|
-
"location" : "https://github.com/zhangao0086/DKPhotoGallery",
|
|
25
|
-
"state" : {
|
|
26
|
-
"branch" : "master",
|
|
27
|
-
"revision" : "311c1bc7a94f1538f82773a79c84374b12a2ef3d"
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"identity" : "sdwebimage",
|
|
32
|
-
"kind" : "remoteSourceControl",
|
|
33
|
-
"location" : "https://github.com/SDWebImage/SDWebImage",
|
|
34
|
-
"state" : {
|
|
35
|
-
"revision" : "2de3a496eaf6df9a1312862adcfd54acd73c39c0",
|
|
36
|
-
"version" : "5.21.7"
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
"identity" : "swiftygif",
|
|
41
|
-
"kind" : "remoteSourceControl",
|
|
42
|
-
"location" : "https://github.com/kirualex/SwiftyGif.git",
|
|
43
|
-
"state" : {
|
|
44
|
-
"revision" : "4430cbc148baa3907651d40562d96325426f409a",
|
|
45
|
-
"version" : "5.4.5"
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
"identity" : "tocropviewcontroller",
|
|
50
|
-
"kind" : "remoteSourceControl",
|
|
51
|
-
"location" : "https://github.com/TimOliver/TOCropViewController",
|
|
52
|
-
"state" : {
|
|
53
|
-
"revision" : "d4a6d8100f4b886fdbc8ae399bf144ff3e9afb7e",
|
|
54
|
-
"version" : "2.8.0"
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
],
|
|
58
|
-
"version" : 2
|
|
59
|
-
}
|
ios/Runner.xcworkspace/contents.xcworkspacedata
CHANGED
|
@@ -4,7 +4,4 @@
|
|
|
4
4
|
<FileRef
|
|
5
5
|
location = "group:Runner.xcodeproj">
|
|
6
6
|
</FileRef>
|
|
7
|
-
<FileRef
|
|
8
|
-
location = "group:Pods/Pods.xcodeproj">
|
|
9
|
-
</FileRef>
|
|
10
7
|
</Workspace>
|
ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"pins" : [
|
|
3
|
-
{
|
|
4
|
-
"identity" : "dkcamera",
|
|
5
|
-
"kind" : "remoteSourceControl",
|
|
6
|
-
"location" : "https://github.com/zhangao0086/DKCamera",
|
|
7
|
-
"state" : {
|
|
8
|
-
"branch" : "master",
|
|
9
|
-
"revision" : "5c691d11014b910aff69f960475d70e65d9dcc96"
|
|
10
|
-
}
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
"identity" : "dkimagepickercontroller",
|
|
14
|
-
"kind" : "remoteSourceControl",
|
|
15
|
-
"location" : "https://github.com/zhangao0086/DKImagePickerController",
|
|
16
|
-
"state" : {
|
|
17
|
-
"branch" : "4.3.9",
|
|
18
|
-
"revision" : "0bdfeacefa308545adde07bef86e349186335915"
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
"identity" : "dkphotogallery",
|
|
23
|
-
"kind" : "remoteSourceControl",
|
|
24
|
-
"location" : "https://github.com/zhangao0086/DKPhotoGallery",
|
|
25
|
-
"state" : {
|
|
26
|
-
"branch" : "master",
|
|
27
|
-
"revision" : "311c1bc7a94f1538f82773a79c84374b12a2ef3d"
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"identity" : "sdwebimage",
|
|
32
|
-
"kind" : "remoteSourceControl",
|
|
33
|
-
"location" : "https://github.com/SDWebImage/SDWebImage",
|
|
34
|
-
"state" : {
|
|
35
|
-
"revision" : "2de3a496eaf6df9a1312862adcfd54acd73c39c0",
|
|
36
|
-
"version" : "5.21.7"
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
"identity" : "swiftygif",
|
|
41
|
-
"kind" : "remoteSourceControl",
|
|
42
|
-
"location" : "https://github.com/kirualex/SwiftyGif.git",
|
|
43
|
-
"state" : {
|
|
44
|
-
"revision" : "4430cbc148baa3907651d40562d96325426f409a",
|
|
45
|
-
"version" : "5.4.5"
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
"identity" : "tocropviewcontroller",
|
|
50
|
-
"kind" : "remoteSourceControl",
|
|
51
|
-
"location" : "https://github.com/TimOliver/TOCropViewController",
|
|
52
|
-
"state" : {
|
|
53
|
-
"revision" : "d4a6d8100f4b886fdbc8ae399bf144ff3e9afb7e",
|
|
54
|
-
"version" : "2.8.0"
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
],
|
|
58
|
-
"version" : 2
|
|
59
|
-
}
|
ios/Runner/Info.plist
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
<key>CFBundleSignature</key>
|
|
24
24
|
<string>????</string>
|
|
25
25
|
<key>CFBundleVersion</key>
|
|
26
|
-
<string>
|
|
26
|
+
<string>8</string>
|
|
27
27
|
<key>LSRequiresIPhoneOS</key>
|
|
28
28
|
<true/>
|
|
29
29
|
<key>UIApplicationSceneManifest</key>
|
|
@@ -70,9 +70,5 @@
|
|
|
70
70
|
<false/>
|
|
71
71
|
<key>ITSAppUsesNonExemptEncryption</key>
|
|
72
72
|
<false/>
|
|
73
|
-
<key>NSPhotoLibraryUsageDescription</key>
|
|
74
|
-
<string>Only Bible App uses the system file picker to let you choose a backup file to restore your highlights and settings. Depending on your device, this picker may show your photo library, though Only Bible App does not access your photos directly.</string>
|
|
75
|
-
<key>NSAppleMusicUsageDescription</key>
|
|
76
|
-
<string>Only Bible App does not access your music library. This description is required because a bundled library references media library APIs, even though this app does not use them.</string>
|
|
77
73
|
</dict>
|
|
78
74
|
</plist>
|
ios/fastlane/Fastfile
CHANGED
|
@@ -40,8 +40,8 @@ platform :ios do
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
desc "Push a new release build to the App Store"
|
|
43
|
-
lane :release do
|
|
43
|
+
lane :release do |options|
|
|
44
|
-
screenshots
|
|
44
|
+
screenshots unless options[:skip_screenshots]
|
|
45
45
|
certs
|
|
46
46
|
increment_build_number(xcodeproj: "Runner.xcodeproj")
|
|
47
47
|
build_app(
|
|
@@ -54,7 +54,11 @@ platform :ios do
|
|
|
54
54
|
}
|
|
55
55
|
)
|
|
56
56
|
api_key = api_key()
|
|
57
|
+
upload_to_app_store(
|
|
58
|
+
api_key: api_key,
|
|
57
|
-
|
|
59
|
+
overwrite_screenshots: true,
|
|
60
|
+
precheck_include_in_app_purchases: false
|
|
61
|
+
)
|
|
58
62
|
end
|
|
59
63
|
|
|
60
64
|
desc "Upload a pre-built IPA to the App Store"
|
lib/app.dart
CHANGED
|
@@ -5,7 +5,7 @@ import "package:only_bible_app/home.dart";
|
|
|
5
5
|
import "package:only_bible_app/store/app_navigator.dart";
|
|
6
6
|
import "package:only_bible_app/store/app_state.dart";
|
|
7
7
|
import "package:only_bible_app/store/actions_navigation.dart";
|
|
8
|
-
import "package:
|
|
8
|
+
import "package:only_bible_app/theme.dart";
|
|
9
9
|
|
|
10
10
|
class App extends StatelessWidget {
|
|
11
11
|
final GlobalKey<NavigatorState> globalNavigatorKey;
|
lib/dialog.dart
CHANGED
|
@@ -6,7 +6,7 @@ import "package:only_bible_app/gen/bible.gen.dart";
|
|
|
6
6
|
import "package:only_bible_app/store/actions_navigation.dart";
|
|
7
7
|
import "package:only_bible_app/store/app_navigator.dart";
|
|
8
8
|
import "package:only_bible_app/store/app_state.dart";
|
|
9
|
-
export "package:
|
|
9
|
+
export "package:only_bible_app/dialogs.dart" show showAlert, showError, showConfirm;
|
|
10
10
|
|
|
11
11
|
void showBibleSelectDialog(BuildContext context, Bible bible) {
|
|
12
12
|
showDialog(
|
lib/dialogs.dart
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import "package:flutter/material.dart";
|
|
2
|
+
|
|
3
|
+
void showAlert(BuildContext context, String title, String message) {
|
|
4
|
+
showDialog(
|
|
5
|
+
context: context,
|
|
6
|
+
barrierColor: Colors.black54,
|
|
7
|
+
builder: (_) {
|
|
8
|
+
return AlertDialog(
|
|
9
|
+
title: Text(title),
|
|
10
|
+
content: Text(message),
|
|
11
|
+
actionsAlignment: MainAxisAlignment.center,
|
|
12
|
+
actionsOverflowButtonSpacing: 8.0,
|
|
13
|
+
actions: [
|
|
14
|
+
TextButton(
|
|
15
|
+
onPressed: () {
|
|
16
|
+
Navigator.of(context).pop();
|
|
17
|
+
},
|
|
18
|
+
child: const Text("OK"),
|
|
19
|
+
),
|
|
20
|
+
],
|
|
21
|
+
);
|
|
22
|
+
},
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
void showError(BuildContext context, String message) {
|
|
27
|
+
showAlert(context, "Error", message);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
Future<bool> showConfirm(
|
|
31
|
+
BuildContext context,
|
|
32
|
+
String title,
|
|
33
|
+
String message,
|
|
34
|
+
) async {
|
|
35
|
+
const buttonStyle = ButtonStyle(
|
|
36
|
+
padding: WidgetStatePropertyAll(
|
|
37
|
+
EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
|
38
|
+
),
|
|
39
|
+
textStyle: WidgetStatePropertyAll(TextStyle(fontSize: 13)),
|
|
40
|
+
);
|
|
41
|
+
final result = await showDialog<bool>(
|
|
42
|
+
context: context,
|
|
43
|
+
barrierColor: Colors.black54,
|
|
44
|
+
builder: (dialogContext) {
|
|
45
|
+
return AlertDialog(
|
|
46
|
+
title: Text(title),
|
|
47
|
+
content: Text(message),
|
|
48
|
+
actionsAlignment: MainAxisAlignment.center,
|
|
49
|
+
actionsOverflowButtonSpacing: 8.0,
|
|
50
|
+
actions: [
|
|
51
|
+
TextButton(
|
|
52
|
+
style: buttonStyle,
|
|
53
|
+
onPressed: () => Navigator.of(dialogContext).pop(false),
|
|
54
|
+
child: const Text("Cancel"),
|
|
55
|
+
),
|
|
56
|
+
FilledButton(
|
|
57
|
+
style: buttonStyle,
|
|
58
|
+
onPressed: () => Navigator.of(dialogContext).pop(true),
|
|
59
|
+
child: const Text("Continue"),
|
|
60
|
+
),
|
|
61
|
+
],
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
return result ?? false;
|
|
66
|
+
}
|
lib/error_reporting.dart
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import "dart:async";
|
|
2
|
+
import "dart:convert";
|
|
3
|
+
|
|
4
|
+
import "package:flutter/foundation.dart";
|
|
5
|
+
import "package:flutter/material.dart";
|
|
6
|
+
import "package:flutter/scheduler.dart";
|
|
7
|
+
import "package:http/http.dart" as http;
|
|
8
|
+
|
|
9
|
+
/// Sends (or otherwise records) a single error report. Implementations
|
|
10
|
+
/// should be best-effort — a failed report must never throw back into the
|
|
11
|
+
/// error handler that triggered it.
|
|
12
|
+
typedef ErrorReportSender =
|
|
13
|
+
Future<void> Function(String message, StackTrace? stack);
|
|
14
|
+
|
|
15
|
+
/// Global crash/error reporting: wires [FlutterError.onError] (framework
|
|
16
|
+
/// errors) and [PlatformDispatcher.onError] (uncaught async/platform
|
|
17
|
+
/// errors), shows a user-consent dialog before reporting in release builds,
|
|
18
|
+
/// and just logs to the console in debug builds.
|
|
19
|
+
class ErrorReporting {
|
|
20
|
+
ErrorReporting._();
|
|
21
|
+
|
|
22
|
+
static GlobalKey<NavigatorState>? _navigatorKey;
|
|
23
|
+
static ErrorReportSender? _onReport;
|
|
24
|
+
static Set<String> _ignoredSubstrings = const {};
|
|
25
|
+
|
|
26
|
+
/// Call once at app startup, after `runApp`'s `navigatorKey` exists.
|
|
27
|
+
static void install({
|
|
28
|
+
required GlobalKey<NavigatorState> navigatorKey,
|
|
29
|
+
required ErrorReportSender onReport,
|
|
30
|
+
Set<String> ignoredSubstrings = const {},
|
|
31
|
+
}) {
|
|
32
|
+
_navigatorKey = navigatorKey;
|
|
33
|
+
_onReport = onReport;
|
|
34
|
+
_ignoredSubstrings = ignoredSubstrings;
|
|
35
|
+
|
|
36
|
+
FlutterError.onError = (errorDetails) {
|
|
37
|
+
FlutterError.presentError(errorDetails);
|
|
38
|
+
_handle(errorDetails.exception.toString(), errorDetails.stack);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
PlatformDispatcher.instance.onError = (error, stack) {
|
|
42
|
+
_handle(error.toString(), stack);
|
|
43
|
+
return true;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Manually reports [message] through the same sender configured via
|
|
48
|
+
/// [install], bypassing the consent dialog — for call sites that already
|
|
49
|
+
/// caught and handled a specific error and just want it recorded (e.g.
|
|
50
|
+
/// only-bible-app's audio-playback failure handler).
|
|
51
|
+
static Future<void> report(String message, StackTrace? stack) async {
|
|
52
|
+
if (kDebugMode) {
|
|
53
|
+
debugPrint("Error: $message");
|
|
54
|
+
debugPrint("$stack");
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
await _onReport?.call(message, stack);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
static void _handle(String message, StackTrace? stack) {
|
|
61
|
+
if (kDebugMode) {
|
|
62
|
+
debugPrint("Error: $message");
|
|
63
|
+
debugPrint("$stack");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (_ignoredSubstrings.any(message.contains)) {
|
|
67
|
+
// Known framework noise: still recorded for visibility, just not
|
|
68
|
+
// worth interrupting the user with a dialog they can't act on.
|
|
69
|
+
unawaited(_onReport?.call(message, stack));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
SchedulerBinding.instance.addPostFrameCallback((_) {
|
|
73
|
+
final context = _navigatorKey?.currentContext;
|
|
74
|
+
if (context == null || !context.mounted) return;
|
|
75
|
+
_showReportDialog(context, message, stack);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static void _showReportDialog(
|
|
80
|
+
BuildContext context,
|
|
81
|
+
String message,
|
|
82
|
+
StackTrace? stack,
|
|
83
|
+
) {
|
|
84
|
+
showDialog<void>(
|
|
85
|
+
context: context,
|
|
86
|
+
barrierColor: Colors.black54,
|
|
87
|
+
builder: (dialogContext) => AlertDialog(
|
|
88
|
+
title: const Text("Alert"),
|
|
89
|
+
content: const Text(
|
|
90
|
+
"An error has occurred. Do you want to report this error to us?",
|
|
91
|
+
),
|
|
92
|
+
actionsAlignment: MainAxisAlignment.end,
|
|
93
|
+
actionsOverflowButtonSpacing: 8.0,
|
|
94
|
+
actions: [
|
|
95
|
+
TextButton(
|
|
96
|
+
onPressed: () {
|
|
97
|
+
Navigator.of(dialogContext).pop();
|
|
98
|
+
unawaited(_onReport?.call(message, stack));
|
|
99
|
+
},
|
|
100
|
+
child: const Text("Yes"),
|
|
101
|
+
),
|
|
102
|
+
TextButton(
|
|
103
|
+
onPressed: () => Navigator.of(dialogContext).pop(),
|
|
104
|
+
child: const Text("No"),
|
|
105
|
+
),
|
|
106
|
+
],
|
|
107
|
+
),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// Builds an [ErrorReportSender] that POSTs to the shared `error-reporter`
|
|
113
|
+
/// Cloudflare Worker (see workers/error-reporter/), which emails the report
|
|
114
|
+
/// via Cloudflare Email Sending. [sharedSecret], if provided, is sent as
|
|
115
|
+
/// `X-Report-Secret` — this is NOT a real security boundary (it's embedded
|
|
116
|
+
/// client-side, same as any app secret), it only filters out generic bots
|
|
117
|
+
/// blindly probing the endpoint. Real abuse resistance is the Worker's own
|
|
118
|
+
/// rate limiter.
|
|
119
|
+
ErrorReportSender httpErrorReportSender({
|
|
120
|
+
required Uri endpoint,
|
|
121
|
+
required String appName,
|
|
122
|
+
String? sharedSecret,
|
|
123
|
+
String? appVersion,
|
|
124
|
+
String? buildNumber,
|
|
125
|
+
}) {
|
|
126
|
+
return (message, stack) async {
|
|
127
|
+
try {
|
|
128
|
+
await http.post(
|
|
129
|
+
endpoint,
|
|
130
|
+
headers: {
|
|
131
|
+
"Content-Type": "application/json",
|
|
132
|
+
if (sharedSecret != null) "X-Report-Secret": sharedSecret,
|
|
133
|
+
},
|
|
134
|
+
body: jsonEncode({
|
|
135
|
+
"app": appName,
|
|
136
|
+
"message": message,
|
|
137
|
+
"stack": stack?.toString(),
|
|
138
|
+
"platform": defaultTargetPlatform.name,
|
|
139
|
+
"appVersion": appVersion,
|
|
140
|
+
"buildNumber": buildNumber,
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
143
|
+
} catch (_) {
|
|
144
|
+
// Best-effort — a failed report must not cascade into another error.
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
lib/main.dart
CHANGED
|
@@ -9,8 +9,8 @@ import "package:only_bible_app/app.dart";
|
|
|
9
9
|
import "package:only_bible_app/env.dart";
|
|
10
10
|
import "package:only_bible_app/store/app_persistor.dart";
|
|
11
11
|
import "package:only_bible_app/store/app_state.dart";
|
|
12
|
+
import "package:only_bible_app/error_reporting.dart";
|
|
12
13
|
import "package:only_bible_app/utils.dart";
|
|
13
|
-
import "package:shared/shared.dart" show ErrorReporting, httpErrorReportSender;
|
|
14
14
|
|
|
15
15
|
// TODO: replace with the deployed error-reporter Worker's actual URL (see
|
|
16
16
|
// workers/error-reporter/) — either its workers.dev subdomain or a custom
|
lib/store/actions_backup.dart
CHANGED
|
@@ -2,13 +2,15 @@ import "dart:convert";
|
|
|
2
2
|
import "dart:io";
|
|
3
3
|
|
|
4
4
|
import "package:async_redux/async_redux.dart";
|
|
5
|
-
import "package:
|
|
5
|
+
import "package:file_selector/file_selector.dart";
|
|
6
6
|
import "package:flutter/foundation.dart";
|
|
7
7
|
import "package:flutter/material.dart";
|
|
8
8
|
import "package:only_bible_app/dialog.dart";
|
|
9
9
|
import "package:only_bible_app/gen/bible.gen.dart";
|
|
10
10
|
import "package:only_bible_app/store/app_state.dart";
|
|
11
11
|
import "package:only_bible_app/utils.dart";
|
|
12
|
+
import "package:path_provider/path_provider.dart";
|
|
13
|
+
import "package:share_plus/share_plus.dart";
|
|
12
14
|
|
|
13
15
|
String buildBackupFileName([DateTime? now]) {
|
|
14
16
|
final n = now ?? DateTime.now();
|
|
@@ -70,21 +72,33 @@ class ExportAppStateAction extends ReduxAction<AppState> {
|
|
|
70
72
|
Future<AppState?> reduce() async {
|
|
71
73
|
final jsonString = buildBackupJson(state);
|
|
72
74
|
final fileName = buildBackupFileName();
|
|
73
|
-
//
|
|
75
|
+
// file_selector's save-location dialog isn't supported on Android/iOS
|
|
74
|
-
// the chosen path; we then write the file manually.
|
|
75
|
-
//
|
|
76
|
+
// (only desktop and web), so mobile exports go through the share sheet
|
|
76
|
-
//
|
|
77
|
+
// (Save to Files, AirDrop, etc.) instead.
|
|
77
|
-
final isDesktop = !kIsWeb && (Platform.isMacOS || Platform.isWindows || Platform.isLinux);
|
|
78
|
-
final
|
|
78
|
+
final canPickSaveLocation = kIsWeb || isDesktop;
|
|
79
|
-
String? path;
|
|
80
79
|
try {
|
|
80
|
+
if (canPickSaveLocation) {
|
|
81
|
-
|
|
81
|
+
final location = await getSaveLocation(
|
|
82
|
-
|
|
82
|
+
suggestedName: fileName,
|
|
83
|
-
|
|
83
|
+
acceptedTypeGroups: const [
|
|
84
|
+
XTypeGroup(label: "JSON", extensions: ["json"]),
|
|
85
|
+
],
|
|
84
|
-
|
|
86
|
+
);
|
|
85
|
-
|
|
87
|
+
if (location == null) return null; // user cancelled
|
|
88
|
+
final file = XFile.fromData(
|
|
89
|
+
utf8.encode(jsonString),
|
|
90
|
+
mimeType: "application/json",
|
|
91
|
+
name: fileName,
|
|
92
|
+
);
|
|
93
|
+
await file.saveTo(location.path);
|
|
86
|
-
|
|
94
|
+
} else {
|
|
95
|
+
final dir = await getTemporaryDirectory();
|
|
96
|
+
final path = "${dir.path}/$fileName";
|
|
87
97
|
await File(path).writeAsString(jsonString);
|
|
98
|
+
final result = await SharePlus.instance.share(
|
|
99
|
+
ShareParams(files: [XFile(path)]),
|
|
100
|
+
);
|
|
101
|
+
if (result.status == ShareResultStatus.dismissed) return null;
|
|
88
102
|
}
|
|
89
103
|
} catch (_) {
|
|
90
104
|
if (buildContext.mounted) {
|
|
@@ -93,7 +107,11 @@ class ExportAppStateAction extends ReduxAction<AppState> {
|
|
|
93
107
|
return null;
|
|
94
108
|
}
|
|
95
109
|
if (buildContext.mounted) {
|
|
110
|
+
showAlert(
|
|
111
|
+
buildContext,
|
|
112
|
+
"Backup Exported",
|
|
96
|
-
|
|
113
|
+
"Your backup was saved successfully.",
|
|
114
|
+
);
|
|
97
115
|
}
|
|
98
116
|
return null;
|
|
99
117
|
}
|
|
@@ -114,25 +132,22 @@ class ImportAppStateAction extends ReduxAction<AppState> {
|
|
|
114
132
|
if (!confirmed) return null;
|
|
115
133
|
if (!buildContext.mounted) return null;
|
|
116
134
|
|
|
117
|
-
final
|
|
135
|
+
final file = await openFile(
|
|
118
|
-
|
|
136
|
+
acceptedTypeGroups: const [
|
|
119
|
-
|
|
137
|
+
XTypeGroup(label: "JSON", extensions: ["json"]),
|
|
120
|
-
|
|
138
|
+
],
|
|
121
139
|
);
|
|
122
|
-
if (
|
|
140
|
+
if (file == null) return null; // user cancelled
|
|
123
|
-
|
|
124
|
-
final bytes = result.files.single.bytes;
|
|
125
|
-
if (bytes == null) {
|
|
126
|
-
if (buildContext.mounted) {
|
|
127
|
-
showError(buildContext, "That file isn't a valid backup");
|
|
128
|
-
}
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
141
|
|
|
132
142
|
try {
|
|
143
|
+
final bytes = await file.readAsBytes();
|
|
133
144
|
final newState = await parseBackupState(utf8.decode(bytes));
|
|
134
145
|
if (buildContext.mounted) {
|
|
146
|
+
showAlert(
|
|
147
|
+
buildContext,
|
|
148
|
+
"Backup Imported",
|
|
135
|
-
|
|
149
|
+
"Your backup was restored successfully.",
|
|
150
|
+
);
|
|
136
151
|
}
|
|
137
152
|
return newState;
|
|
138
153
|
} on BackupParseException catch (err) {
|
|
@@ -140,6 +155,11 @@ class ImportAppStateAction extends ReduxAction<AppState> {
|
|
|
140
155
|
showError(buildContext, err.message);
|
|
141
156
|
}
|
|
142
157
|
return null;
|
|
158
|
+
} catch (_) {
|
|
159
|
+
if (buildContext.mounted) {
|
|
160
|
+
showError(buildContext, "That file isn't a valid backup");
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
143
163
|
}
|
|
144
164
|
}
|
|
145
165
|
}
|
lib/store/actions_state.dart
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import "dart:developer";
|
|
2
2
|
|
|
3
3
|
import "package:async_redux/async_redux.dart";
|
|
4
|
-
import "package:flutter_soloud/flutter_soloud.dart";
|
|
5
4
|
import "package:flutter/material.dart";
|
|
6
5
|
import "package:only_bible_app/dialog.dart";
|
|
6
|
+
import "package:only_bible_app/error_reporting.dart";
|
|
7
7
|
import "package:only_bible_app/gen/bible.gen.dart";
|
|
8
8
|
import "package:only_bible_app/store/app_state.dart";
|
|
9
9
|
import "package:only_bible_app/utils.dart";
|
|
10
|
-
import "package:shared/shared.dart" show ErrorReporting;
|
|
11
10
|
|
|
12
11
|
class ToggleEngTitlesAction extends ReduxAction<AppState> {
|
|
13
12
|
@override
|
|
@@ -79,32 +78,21 @@ class TogglePlayAction extends ReduxAction<AppState> {
|
|
|
79
78
|
final pathname = "${bible.name!}_${v.book}_${v.chapter}_${v.index}";
|
|
80
79
|
try {
|
|
81
80
|
final data = await convertText(audioVoice, v.text ?? "");
|
|
82
|
-
if (!
|
|
81
|
+
if (!isAudioPlaybackInitialized) break;
|
|
83
|
-
final stream = SoLoud.instance.setBufferStream(
|
|
84
|
-
bufferingType: BufferingType.preserved,
|
|
85
|
-
sampleRate: 24000,
|
|
86
|
-
channels: Channels.mono,
|
|
87
|
-
format: BufferType.s16le,
|
|
88
|
-
);
|
|
89
|
-
|
|
82
|
+
await playAudioClip(data);
|
|
90
|
-
|
|
83
|
+
if (!isAudioPlaybackInitialized) break;
|
|
91
|
-
final handle = await SoLoud.instance.play(stream);
|
|
92
|
-
// Wait for playback to complete
|
|
93
|
-
await stream.soundEvents.firstWhere(
|
|
94
|
-
(e) =>
|
|
95
|
-
(e.event == SoundEventType.handleIsNoMoreValid &&
|
|
96
|
-
e.handle == handle) ||
|
|
97
|
-
e.event == SoundEventType.soundDisposed,
|
|
98
|
-
);
|
|
99
84
|
} catch (err) {
|
|
100
|
-
// If
|
|
85
|
+
// If playback was disposed (user stopped playback), don't treat as error
|
|
101
|
-
if (!
|
|
86
|
+
if (!isAudioPlaybackInitialized) break;
|
|
102
87
|
log(
|
|
103
88
|
"Could not play audio",
|
|
104
89
|
name: "play",
|
|
105
90
|
error: (err.toString(), pathname),
|
|
106
91
|
);
|
|
92
|
+
await ErrorReporting.report(
|
|
107
|
-
|
|
93
|
+
(err.toString(), pathname).toString(),
|
|
94
|
+
null,
|
|
95
|
+
);
|
|
108
96
|
if (buildContext.mounted) {
|
|
109
97
|
showError(buildContext, audioError);
|
|
110
98
|
}
|
|
@@ -137,12 +125,15 @@ class SelectVerseAction extends ReduxAction<AppState> {
|
|
|
137
125
|
);
|
|
138
126
|
final newVerses = isSelected
|
|
139
127
|
? state.selectedVerses
|
|
128
|
+
.where(
|
|
129
|
+
(it) =>
|
|
130
|
+
it.book != verse.book ||
|
|
140
|
-
|
|
131
|
+
it.chapter != verse.chapter ||
|
|
132
|
+
it.index != verse.index,
|
|
133
|
+
)
|
|
141
|
-
|
|
134
|
+
.toList()
|
|
142
135
|
: [...state.selectedVerses, verse];
|
|
143
|
-
return state.copy(
|
|
144
|
-
|
|
136
|
+
return state.copy(selectedVerses: newVerses);
|
|
145
|
-
);
|
|
146
137
|
}
|
|
147
138
|
}
|
|
148
139
|
|
|
@@ -173,8 +164,14 @@ class SetHighlightAction extends ReduxAction<AppState> {
|
|
|
173
164
|
}).toList();
|
|
174
165
|
// Keep last 500 entries
|
|
175
166
|
final updatedHistory = [...state.highlightHistory, ...newEntries];
|
|
167
|
+
final trimmed = updatedHistory.length > 500
|
|
176
|
-
|
|
168
|
+
? updatedHistory.sublist(updatedHistory.length - 500)
|
|
169
|
+
: updatedHistory;
|
|
170
|
+
return state.copy(
|
|
171
|
+
highlights: updated,
|
|
172
|
+
selectedVerses: [],
|
|
177
|
-
|
|
173
|
+
highlightHistory: trimmed,
|
|
174
|
+
);
|
|
178
175
|
}
|
|
179
176
|
}
|
|
180
177
|
|
lib/theme.dart
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import "package:flutter/material.dart";
|
|
2
|
+
|
|
3
|
+
const lightColorScheme = ColorScheme(
|
|
4
|
+
brightness: Brightness.light,
|
|
5
|
+
primary: Color.fromARGB(255, 100, 127, 203),
|
|
6
|
+
onPrimary: Color(0xFFFFFFFF),
|
|
7
|
+
primaryContainer: Color(0xFFDAE2FF),
|
|
8
|
+
onPrimaryContainer: Color(0xFF001848),
|
|
9
|
+
secondary: Color(0xFF4C5C92),
|
|
10
|
+
onSecondary: Color(0xFFFFFFFF),
|
|
11
|
+
secondaryContainer: Color(0xFFDCE1FF),
|
|
12
|
+
onSecondaryContainer: Color(0xFF02174B),
|
|
13
|
+
tertiary: Color(0xFF7D4E7D),
|
|
14
|
+
onTertiary: Color(0xFFFFFFFF),
|
|
15
|
+
tertiaryContainer: Color(0xFFFFD6FA),
|
|
16
|
+
onTertiaryContainer: Color(0xFF320935),
|
|
17
|
+
error: Color(0xFFBA1A1A),
|
|
18
|
+
onError: Color(0xFFFFFFFF),
|
|
19
|
+
errorContainer: Color(0xFFFFDAD6),
|
|
20
|
+
onErrorContainer: Color(0xFF410002),
|
|
21
|
+
surface: Color(0xFFFFFFFF),
|
|
22
|
+
onSurface: Color(0xFF000000),
|
|
23
|
+
onSurfaceVariant: Color(0xFF45464F),
|
|
24
|
+
outline: Color.fromARGB(255, 157, 160, 171),
|
|
25
|
+
outlineVariant: Color(0xFFC5C6D0),
|
|
26
|
+
shadow: Color.fromARGB(255, 88, 85, 85),
|
|
27
|
+
scrim: Color(0xFF000000),
|
|
28
|
+
inverseSurface: Color(0xFF2F3036),
|
|
29
|
+
onInverseSurface: Color(0xFFF1F0F7),
|
|
30
|
+
inversePrimary: Color(0xFFB2C5FF),
|
|
31
|
+
surfaceDim: Color(0xFFDAD9E0),
|
|
32
|
+
surfaceBright: Color(0xFFFAF8FF),
|
|
33
|
+
surfaceContainerLowest: Color(0xFFFFFFFF),
|
|
34
|
+
surfaceContainerLow: Color(0xFFF4F3FA),
|
|
35
|
+
surfaceContainer: Color(0xFFEEEDF4),
|
|
36
|
+
surfaceContainerHigh: Color(0xFFE8E7EF),
|
|
37
|
+
surfaceContainerHighest: Color(0xFFE3E2E9),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const darkColorScheme = ColorScheme(
|
|
41
|
+
brightness: Brightness.dark,
|
|
42
|
+
// Accent: Claude Code permission/suggestion blue-purple — rgb(177,185,249)
|
|
43
|
+
primary: Color(0xFFB1B9F9),
|
|
44
|
+
onPrimary: Color(0xFF1A2050),
|
|
45
|
+
primaryContainer: Color(0xFF2A3580),
|
|
46
|
+
onPrimaryContainer: Color(0xFFDDE0FF),
|
|
47
|
+
secondary: Color(0xFFB1B9F9),
|
|
48
|
+
onSecondary: Color(0xFF1A2050),
|
|
49
|
+
secondaryContainer: Color(0xFF2A3580),
|
|
50
|
+
onSecondaryContainer: Color(0xFFDDE0FF),
|
|
51
|
+
// Tertiary: Claude Code brand orange — rgb(215,119,87)
|
|
52
|
+
tertiary: Color(0xFFD77757),
|
|
53
|
+
onTertiary: Color(0xFF4A1A00),
|
|
54
|
+
tertiaryContainer: Color(0xFF6B3020),
|
|
55
|
+
onTertiaryContainer: Color(0xFFFFDBCC),
|
|
56
|
+
// Error: Claude Code error — rgb(255,107,128)
|
|
57
|
+
error: Color(0xFFFF6B80),
|
|
58
|
+
onError: Color(0xFF690005),
|
|
59
|
+
errorContainer: Color(0xFF8C1D18),
|
|
60
|
+
onErrorContainer: Color(0xFFFFDAD6),
|
|
61
|
+
// Neutral dark gray — matches Claude Code desktop aesthetic (no blue tint)
|
|
62
|
+
surface: Color(0xFF1E1E1E),
|
|
63
|
+
onSurface: Color(0xFFE8E8E8),
|
|
64
|
+
// Secondary text: Claude Code inactive — rgb(153,153,153)
|
|
65
|
+
onSurfaceVariant: Color(0xFF999999),
|
|
66
|
+
outline: Color(0xFF666666),
|
|
67
|
+
outlineVariant: Color(0xFF3A3A3A),
|
|
68
|
+
shadow: Color(0xFF000000),
|
|
69
|
+
scrim: Color(0xFF000000),
|
|
70
|
+
inverseSurface: Color(0xFFE3E3E3),
|
|
71
|
+
onInverseSurface: Color(0xFF303030),
|
|
72
|
+
inversePrimary: Color(0xFF4A5AA0),
|
|
73
|
+
surfaceDim: Color(0xFF181818),
|
|
74
|
+
surfaceBright: Color(0xFF323232),
|
|
75
|
+
surfaceContainerLowest: Color(0xFF141414),
|
|
76
|
+
surfaceContainerLow: Color(0xFF222222),
|
|
77
|
+
surfaceContainer: Color(0xFF262626),
|
|
78
|
+
surfaceContainerHigh: Color(0xFF2C2C2C),
|
|
79
|
+
// Elevated panels: Claude Code userMessageBackground — rgb(55,55,55)
|
|
80
|
+
surfaceContainerHighest: Color(0xFF373737),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
class AppTheme {
|
|
84
|
+
AppTheme._();
|
|
85
|
+
|
|
86
|
+
static final ThemeData light = ThemeData(
|
|
87
|
+
brightness: Brightness.light,
|
|
88
|
+
colorScheme: lightColorScheme,
|
|
89
|
+
useMaterial3: true,
|
|
90
|
+
applyElevationOverlayColor: false,
|
|
91
|
+
textSelectionTheme: TextSelectionThemeData(
|
|
92
|
+
cursorColor: Colors.black,
|
|
93
|
+
selectionHandleColor: Colors.black,
|
|
94
|
+
selectionColor: Colors.grey.shade200,
|
|
95
|
+
),
|
|
96
|
+
inputDecorationTheme: const InputDecorationTheme(
|
|
97
|
+
focusColor: Colors.transparent,
|
|
98
|
+
hoverColor: Colors.transparent,
|
|
99
|
+
activeIndicatorBorder: BorderSide(color: Colors.black),
|
|
100
|
+
hintStyle: TextStyle(color: Colors.grey),
|
|
101
|
+
),
|
|
102
|
+
appBarTheme: AppBarTheme(
|
|
103
|
+
backgroundColor: lightColorScheme.surface,
|
|
104
|
+
foregroundColor: lightColorScheme.onSurface,
|
|
105
|
+
elevation: 1,
|
|
106
|
+
// Material3 otherwise blends in a colorScheme.surfaceTint overlay
|
|
107
|
+
// proportional to elevation, making the AppBar a visibly different
|
|
108
|
+
// shade from the flat Scaffold background behind it.
|
|
109
|
+
surfaceTintColor: Colors.transparent,
|
|
110
|
+
),
|
|
111
|
+
bottomSheetTheme: BottomSheetThemeData(
|
|
112
|
+
elevation: 10,
|
|
113
|
+
backgroundColor: lightColorScheme.surface,
|
|
114
|
+
shadowColor: lightColorScheme.scrim,
|
|
115
|
+
showDragHandle: true,
|
|
116
|
+
dragHandleSize: Size(50, 3),
|
|
117
|
+
),
|
|
118
|
+
dialogTheme: const DialogThemeData(
|
|
119
|
+
elevation: 10,
|
|
120
|
+
shadowColor: Colors.black,
|
|
121
|
+
backgroundColor: Colors.white,
|
|
122
|
+
surfaceTintColor: Colors.white,
|
|
123
|
+
shape: Border(top: BorderSide(width: 1.5, color: Colors.black)),
|
|
124
|
+
),
|
|
125
|
+
iconButtonTheme: IconButtonThemeData(
|
|
126
|
+
style: IconButton.styleFrom(enableFeedback: true),
|
|
127
|
+
),
|
|
128
|
+
textButtonTheme: TextButtonThemeData(
|
|
129
|
+
style: TextButton.styleFrom(
|
|
130
|
+
enableFeedback: true,
|
|
131
|
+
padding: EdgeInsets.zero,
|
|
132
|
+
shape: RoundedRectangleBorder(
|
|
133
|
+
borderRadius: BorderRadius.circular(4),
|
|
134
|
+
side: BorderSide(color: Colors.black, width: 1),
|
|
135
|
+
),
|
|
136
|
+
elevation: 2,
|
|
137
|
+
shadowColor: Colors.black,
|
|
138
|
+
backgroundColor: lightColorScheme.surfaceContainerLow,
|
|
139
|
+
foregroundColor: Colors.black,
|
|
140
|
+
textStyle: const TextStyle(
|
|
141
|
+
fontSize: 18,
|
|
142
|
+
fontWeight: FontWeight.w500,
|
|
143
|
+
letterSpacing: 0,
|
|
144
|
+
),
|
|
145
|
+
),
|
|
146
|
+
),
|
|
147
|
+
segmentedButtonTheme: SegmentedButtonThemeData(
|
|
148
|
+
style: ButtonStyle(
|
|
149
|
+
backgroundColor: WidgetStateProperty.resolveWith((states) {
|
|
150
|
+
if (states.contains(WidgetState.selected)) {
|
|
151
|
+
return lightColorScheme.primary;
|
|
152
|
+
}
|
|
153
|
+
return Colors.grey.shade100;
|
|
154
|
+
}),
|
|
155
|
+
foregroundColor: WidgetStateProperty.resolveWith((states) {
|
|
156
|
+
if (states.contains(WidgetState.selected)) {
|
|
157
|
+
return lightColorScheme.onPrimary;
|
|
158
|
+
}
|
|
159
|
+
return lightColorScheme.onSurface;
|
|
160
|
+
}),
|
|
161
|
+
textStyle: WidgetStatePropertyAll(
|
|
162
|
+
TextStyle(
|
|
163
|
+
fontSize: 16,
|
|
164
|
+
fontWeight: FontWeight.w700,
|
|
165
|
+
color: lightColorScheme.onSurface,
|
|
166
|
+
letterSpacing: 0,
|
|
167
|
+
),
|
|
168
|
+
),
|
|
169
|
+
),
|
|
170
|
+
),
|
|
171
|
+
toggleButtonsTheme: ToggleButtonsThemeData(
|
|
172
|
+
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
|
173
|
+
borderColor: lightColorScheme.scrim,
|
|
174
|
+
selectedBorderColor: lightColorScheme.scrim,
|
|
175
|
+
selectedColor: lightColorScheme.onPrimary,
|
|
176
|
+
fillColor: lightColorScheme.primary,
|
|
177
|
+
color: lightColorScheme.onSurface,
|
|
178
|
+
constraints: const BoxConstraints(minHeight: 40, minWidth: 140),
|
|
179
|
+
),
|
|
180
|
+
textTheme: TextTheme(
|
|
181
|
+
bodyMedium: TextStyle(
|
|
182
|
+
fontSize: 16,
|
|
183
|
+
fontWeight: FontWeight.w400,
|
|
184
|
+
wordSpacing: 0,
|
|
185
|
+
letterSpacing: 0,
|
|
186
|
+
color: lightColorScheme.onSurface,
|
|
187
|
+
),
|
|
188
|
+
bodySmall: TextStyle(
|
|
189
|
+
fontSize: 14,
|
|
190
|
+
fontWeight: FontWeight.w400,
|
|
191
|
+
wordSpacing: 0,
|
|
192
|
+
letterSpacing: 0,
|
|
193
|
+
color: lightColorScheme.onSurface,
|
|
194
|
+
),
|
|
195
|
+
headlineLarge: TextStyle(
|
|
196
|
+
fontSize: 38,
|
|
197
|
+
fontWeight: FontWeight.w700,
|
|
198
|
+
color: lightColorScheme.secondary,
|
|
199
|
+
),
|
|
200
|
+
headlineMedium: TextStyle(
|
|
201
|
+
color: lightColorScheme.onSurface,
|
|
202
|
+
fontSize: 24,
|
|
203
|
+
fontWeight: FontWeight.w500,
|
|
204
|
+
letterSpacing: 0.5,
|
|
205
|
+
),
|
|
206
|
+
labelMedium: TextStyle(
|
|
207
|
+
fontSize: 13,
|
|
208
|
+
fontWeight: FontWeight.w700,
|
|
209
|
+
color: Color(0xFF9A1111),
|
|
210
|
+
letterSpacing: 0,
|
|
211
|
+
),
|
|
212
|
+
labelLarge: TextStyle(
|
|
213
|
+
fontSize: 16,
|
|
214
|
+
fontWeight: FontWeight.w700,
|
|
215
|
+
color: lightColorScheme.onSurface,
|
|
216
|
+
letterSpacing: 0,
|
|
217
|
+
),
|
|
218
|
+
),
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
// Built from its own ThemeData(colorScheme: ...) root rather than
|
|
222
|
+
// light.copyWith(colorScheme: darkColorScheme) — copyWith only overrides
|
|
223
|
+
// the fields listed explicitly here, it never re-derives Material3
|
|
224
|
+
// defaults (scaffoldBackgroundColor, canvasColor, cardColor, dividerColor,
|
|
225
|
+
// ...) from the new colorScheme. Starting from light's already-resolved
|
|
226
|
+
// ThemeData left those pinned to the light scheme's white surface, which
|
|
227
|
+
// is why the Scaffold body stayed white in dark mode.
|
|
228
|
+
static final ThemeData dark =
|
|
229
|
+
ThemeData(
|
|
230
|
+
brightness: Brightness.dark,
|
|
231
|
+
colorScheme: darkColorScheme,
|
|
232
|
+
useMaterial3: true,
|
|
233
|
+
applyElevationOverlayColor: false,
|
|
234
|
+
).copyWith(
|
|
235
|
+
inputDecorationTheme: InputDecorationTheme(
|
|
236
|
+
focusColor: Colors.transparent,
|
|
237
|
+
hoverColor: Colors.transparent,
|
|
238
|
+
activeIndicatorBorder: BorderSide(color: darkColorScheme.outline),
|
|
239
|
+
hintStyle: const TextStyle(color: Colors.grey),
|
|
240
|
+
),
|
|
241
|
+
segmentedButtonTheme: SegmentedButtonThemeData(
|
|
242
|
+
style: ButtonStyle(
|
|
243
|
+
backgroundColor: WidgetStateProperty.resolveWith((states) {
|
|
244
|
+
if (states.contains(WidgetState.selected)) {
|
|
245
|
+
return darkColorScheme.primary;
|
|
246
|
+
}
|
|
247
|
+
return darkColorScheme.surfaceContainerHigh;
|
|
248
|
+
}),
|
|
249
|
+
foregroundColor: WidgetStateProperty.resolveWith((states) {
|
|
250
|
+
if (states.contains(WidgetState.selected)) {
|
|
251
|
+
return darkColorScheme.onPrimary;
|
|
252
|
+
}
|
|
253
|
+
return darkColorScheme.onSurface;
|
|
254
|
+
}),
|
|
255
|
+
textStyle: WidgetStatePropertyAll(
|
|
256
|
+
TextStyle(
|
|
257
|
+
fontSize: 16,
|
|
258
|
+
fontWeight: FontWeight.w700,
|
|
259
|
+
color: darkColorScheme.onSurface,
|
|
260
|
+
letterSpacing: 0,
|
|
261
|
+
),
|
|
262
|
+
),
|
|
263
|
+
),
|
|
264
|
+
),
|
|
265
|
+
toggleButtonsTheme: ToggleButtonsThemeData(
|
|
266
|
+
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
|
267
|
+
borderColor: darkColorScheme.outline,
|
|
268
|
+
selectedBorderColor: darkColorScheme.outline,
|
|
269
|
+
selectedColor: darkColorScheme.onPrimary,
|
|
270
|
+
fillColor: darkColorScheme.primary,
|
|
271
|
+
color: darkColorScheme.onSurface,
|
|
272
|
+
constraints: const BoxConstraints(minHeight: 40, minWidth: 140),
|
|
273
|
+
),
|
|
274
|
+
appBarTheme: AppBarTheme(
|
|
275
|
+
backgroundColor: darkColorScheme.surface,
|
|
276
|
+
foregroundColor: darkColorScheme.onSurface,
|
|
277
|
+
elevation: 1,
|
|
278
|
+
surfaceTintColor: Colors.transparent,
|
|
279
|
+
),
|
|
280
|
+
bottomSheetTheme: light.bottomSheetTheme.copyWith(
|
|
281
|
+
backgroundColor: darkColorScheme.surfaceContainerLow,
|
|
282
|
+
shadowColor: Colors.white,
|
|
283
|
+
surfaceTintColor: darkColorScheme.surfaceContainerLow,
|
|
284
|
+
),
|
|
285
|
+
textSelectionTheme: TextSelectionThemeData(
|
|
286
|
+
cursorColor: Colors.white,
|
|
287
|
+
selectionHandleColor: Colors.white,
|
|
288
|
+
selectionColor: Colors.blueGrey.shade800,
|
|
289
|
+
),
|
|
290
|
+
dialogTheme: DialogThemeData(
|
|
291
|
+
backgroundColor: darkColorScheme.surface,
|
|
292
|
+
elevation: 1,
|
|
293
|
+
shape: Border(
|
|
294
|
+
top: BorderSide(width: 1.5, color: darkColorScheme.outline),
|
|
295
|
+
),
|
|
296
|
+
),
|
|
297
|
+
iconButtonTheme: IconButtonThemeData(
|
|
298
|
+
style: IconButton.styleFrom(
|
|
299
|
+
enableFeedback: true,
|
|
300
|
+
foregroundColor: darkColorScheme.onSurface,
|
|
301
|
+
),
|
|
302
|
+
),
|
|
303
|
+
textButtonTheme: TextButtonThemeData(
|
|
304
|
+
style: TextButton.styleFrom(
|
|
305
|
+
enableFeedback: light.textButtonTheme.style!.enableFeedback,
|
|
306
|
+
padding: EdgeInsets.all(0),
|
|
307
|
+
shape: RoundedRectangleBorder(
|
|
308
|
+
borderRadius: BorderRadius.circular(4),
|
|
309
|
+
side: BorderSide(color: darkColorScheme.onSurface, width: 1),
|
|
310
|
+
),
|
|
311
|
+
textStyle: const TextStyle(
|
|
312
|
+
fontSize: 18,
|
|
313
|
+
fontWeight: FontWeight.w500,
|
|
314
|
+
letterSpacing: 0,
|
|
315
|
+
),
|
|
316
|
+
elevation: 0,
|
|
317
|
+
shadowColor: Colors.white,
|
|
318
|
+
backgroundColor: darkColorScheme.surfaceContainerLow,
|
|
319
|
+
foregroundColor: darkColorScheme.primary,
|
|
320
|
+
),
|
|
321
|
+
),
|
|
322
|
+
textTheme: TextTheme(
|
|
323
|
+
bodyMedium: light.textTheme.bodyMedium!.copyWith(
|
|
324
|
+
color: darkColorScheme.onSurface,
|
|
325
|
+
),
|
|
326
|
+
bodySmall: light.textTheme.bodyMedium!.copyWith(
|
|
327
|
+
color: darkColorScheme.onSurface,
|
|
328
|
+
),
|
|
329
|
+
headlineLarge: light.textTheme.headlineLarge!.copyWith(
|
|
330
|
+
color: darkColorScheme.onSurface,
|
|
331
|
+
),
|
|
332
|
+
headlineMedium: light.textTheme.headlineMedium!.copyWith(
|
|
333
|
+
color: darkColorScheme.onSurface,
|
|
334
|
+
),
|
|
335
|
+
labelMedium: light.textTheme.labelMedium!.copyWith(
|
|
336
|
+
color: darkColorScheme.error,
|
|
337
|
+
),
|
|
338
|
+
labelLarge: light.textTheme.labelLarge!.copyWith(
|
|
339
|
+
color: darkColorScheme.onSurface,
|
|
340
|
+
),
|
|
341
|
+
),
|
|
342
|
+
);
|
|
343
|
+
}
|
lib/utils.dart
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import "package:archive/archive.dart";
|
|
2
|
+
import "package:audioplayers/audioplayers.dart";
|
|
2
3
|
import "package:flutter/services.dart";
|
|
3
4
|
import "package:flutter/foundation.dart"
|
|
4
5
|
show TargetPlatform, compute, defaultTargetPlatform;
|
|
5
6
|
import "package:flutter/material.dart";
|
|
6
7
|
import "package:flutter_azure_tts/flutter_azure_tts.dart";
|
|
7
|
-
import "package:flutter_soloud/flutter_soloud.dart";
|
|
8
8
|
import "package:only_bible_app/gen/bible.gen.dart";
|
|
9
9
|
export "package:only_bible_app/bible_data.dart";
|
|
10
10
|
|
|
@@ -35,16 +35,25 @@ extension BibleExt on Bible {
|
|
|
35
35
|
(int, int) clampBookChapter(Bible bible, int book, int chapter) {
|
|
36
36
|
final books = bible.books ?? const [];
|
|
37
37
|
if (books.isEmpty) return (0, 0);
|
|
38
|
+
final clampedBook = book < 0
|
|
39
|
+
? 0
|
|
38
|
-
|
|
40
|
+
: (book >= books.length ? books.length - 1 : book);
|
|
39
41
|
final chapters = books[clampedBook].chapters ?? const [];
|
|
40
42
|
if (chapters.isEmpty) return (clampedBook, 0);
|
|
43
|
+
final clampedChapter = chapter < 0
|
|
44
|
+
? 0
|
|
41
|
-
|
|
45
|
+
: (chapter >= chapters.length ? chapters.length - 1 : chapter);
|
|
42
46
|
return (clampedBook, clampedChapter);
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
/// Returns the (book, chapter) adjacent to [book]/[chapter], stepping forward
|
|
46
50
|
/// or backward across book boundaries, or null if already at the very start/end.
|
|
47
|
-
(int, int)? adjacentChapter(
|
|
51
|
+
(int, int)? adjacentChapter(
|
|
52
|
+
Bible bible,
|
|
53
|
+
int book,
|
|
54
|
+
int chapter, {
|
|
55
|
+
required bool forward,
|
|
56
|
+
}) {
|
|
48
57
|
final selectedBook = bible.books![book];
|
|
49
58
|
if (forward) {
|
|
50
59
|
if (selectedBook.chapters!.length > chapter + 1) {
|
|
@@ -132,13 +141,13 @@ Future<Uint8List> convertText(String langCode, String text) async {
|
|
|
132
141
|
shortName: langCode,
|
|
133
142
|
gender: "",
|
|
134
143
|
locale: langCode,
|
|
135
|
-
sampleRateHertz: AudioOutputFormat.
|
|
144
|
+
sampleRateHertz: AudioOutputFormat.audio24khz48kBitrateMonoMp3,
|
|
136
145
|
voiceType: "",
|
|
137
146
|
status: "",
|
|
138
147
|
styles: [],
|
|
139
148
|
roles: [],
|
|
140
149
|
),
|
|
141
|
-
audioFormat: AudioOutputFormat.
|
|
150
|
+
audioFormat: AudioOutputFormat.audio24khz48kBitrateMonoMp3,
|
|
142
151
|
rate: 0.90,
|
|
143
152
|
text: text,
|
|
144
153
|
),
|
|
@@ -146,15 +155,30 @@ Future<Uint8List> convertText(String langCode, String text) async {
|
|
|
146
155
|
return ttsResponse.audio.buffer.asUint8List();
|
|
147
156
|
}
|
|
148
157
|
|
|
158
|
+
AudioPlayer? _audioPlayer;
|
|
159
|
+
|
|
149
160
|
bool isAudioPlaybackActive() {
|
|
150
|
-
return
|
|
161
|
+
return _audioPlayer?.state == PlayerState.playing;
|
|
151
|
-
SoLoud.instance.getActiveVoiceCount() > 0;
|
|
152
162
|
}
|
|
153
163
|
|
|
164
|
+
bool get isAudioPlaybackInitialized => _audioPlayer != null;
|
|
165
|
+
|
|
154
166
|
Future<void> ensureAudioPlaybackInitialized() async {
|
|
155
|
-
if (!SoLoud.instance.isInitialized) {
|
|
156
|
-
|
|
167
|
+
_audioPlayer ??= AudioPlayer();
|
|
157
|
-
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/// Plays [mp3Data] and waits until it finishes, or until [stopAudioPlayback]
|
|
171
|
+
/// cuts it short. No-ops if playback was already stopped.
|
|
172
|
+
Future<void> playAudioClip(Uint8List mp3Data) async {
|
|
173
|
+
final player = _audioPlayer;
|
|
174
|
+
if (player == null) return;
|
|
175
|
+
await player.play(BytesSource(mp3Data, mimeType: "audio/mpeg"));
|
|
176
|
+
await player.onPlayerStateChanged.firstWhere(
|
|
177
|
+
(s) =>
|
|
178
|
+
s == PlayerState.completed ||
|
|
179
|
+
s == PlayerState.stopped ||
|
|
180
|
+
s == PlayerState.disposed,
|
|
181
|
+
);
|
|
158
182
|
}
|
|
159
183
|
|
|
160
184
|
Future<void>? _audioPlaybackStopFuture;
|
|
@@ -174,14 +198,10 @@ Future<void> stopAudioPlayback() {
|
|
|
174
198
|
}
|
|
175
199
|
|
|
176
200
|
Future<void> _stopAudioPlayback() async {
|
|
201
|
+
final player = _audioPlayer;
|
|
177
|
-
if (
|
|
202
|
+
if (player == null) return;
|
|
178
|
-
|
|
203
|
+
_audioPlayer = null;
|
|
179
|
-
|
|
204
|
+
await player.dispose();
|
|
180
|
-
} finally {
|
|
181
|
-
if (SoLoud.instance.isInitialized) {
|
|
182
|
-
SoLoud.instance.deinit();
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
205
|
}
|
|
186
206
|
|
|
187
207
|
Uint8List _decompressBible(Uint8List compressed) {
|
|
@@ -190,10 +210,13 @@ Uint8List _decompressBible(Uint8List compressed) {
|
|
|
190
210
|
|
|
191
211
|
Future<Bible> loadBible(String name) async {
|
|
192
212
|
final data = await rootBundle.load("assets/bibles/$name.bin.gz");
|
|
193
|
-
final decompressed = await compute(
|
|
213
|
+
final decompressed = await compute(
|
|
214
|
+
_decompressBible,
|
|
215
|
+
data.buffer.asUint8List(),
|
|
216
|
+
);
|
|
194
217
|
return Bible(decompressed);
|
|
195
218
|
}
|
|
196
219
|
|
|
197
|
-
// Error reporting now goes through ErrorReporting.report (
|
|
220
|
+
// Error reporting now goes through ErrorReporting.report (error_reporting.dart) —
|
|
198
221
|
// see main.dart's ErrorReporting.install call and its use in
|
|
199
222
|
// actions_state.dart's audio-playback failure handler.
|
macos/Flutter/Flutter-Debug.xcconfig
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
|
2
1
|
#include "ephemeral/Flutter-Generated.xcconfig"
|
macos/Flutter/Flutter-Release.xcconfig
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
|
2
1
|
#include "ephemeral/Flutter-Generated.xcconfig"
|
macos/Flutter/GeneratedPluginRegistrant.swift
CHANGED
|
@@ -5,18 +5,16 @@
|
|
|
5
5
|
import FlutterMacOS
|
|
6
6
|
import Foundation
|
|
7
7
|
|
|
8
|
+
import audioplayers_darwin
|
|
8
9
|
import connectivity_plus
|
|
9
|
-
import
|
|
10
|
+
import file_selector_macos
|
|
10
|
-
import flutter_secure_storage_darwin
|
|
11
11
|
import package_info_plus
|
|
12
12
|
import share_plus
|
|
13
|
-
import sqflite_darwin
|
|
14
13
|
|
|
15
14
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|
15
|
+
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
|
|
16
16
|
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
|
17
|
-
|
|
17
|
+
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
|
18
|
-
FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin"))
|
|
19
18
|
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
|
20
19
|
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
|
21
|
-
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
|
22
20
|
}
|
macos/Podfile
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
platform :osx, '10.15'
|
|
2
|
-
|
|
3
|
-
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
|
4
|
-
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
|
5
|
-
|
|
6
|
-
project 'Runner', {
|
|
7
|
-
'Debug' => :debug,
|
|
8
|
-
'Profile' => :release,
|
|
9
|
-
'Release' => :release,
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
def flutter_root
|
|
13
|
-
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
|
|
14
|
-
unless File.exist?(generated_xcode_build_settings_path)
|
|
15
|
-
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
File.foreach(generated_xcode_build_settings_path) do |line|
|
|
19
|
-
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
|
20
|
-
return matches[1].strip if matches
|
|
21
|
-
end
|
|
22
|
-
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
|
26
|
-
|
|
27
|
-
flutter_macos_podfile_setup
|
|
28
|
-
|
|
29
|
-
target 'Runner' do
|
|
30
|
-
use_frameworks!
|
|
31
|
-
|
|
32
|
-
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
|
|
33
|
-
target 'RunnerTests' do
|
|
34
|
-
inherit! :search_paths
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
post_install do |installer|
|
|
39
|
-
installer.pods_project.targets.each do |target|
|
|
40
|
-
flutter_additional_macos_build_settings(target)
|
|
41
|
-
end
|
|
42
|
-
end
|
macos/Podfile.lock
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
PODS:
|
|
2
|
-
- flutter_soloud (0.0.1):
|
|
3
|
-
- FlutterMacOS
|
|
4
|
-
- FlutterMacOS (1.0.0)
|
|
5
|
-
|
|
6
|
-
DEPENDENCIES:
|
|
7
|
-
- flutter_soloud (from `Flutter/ephemeral/.symlinks/plugins/flutter_soloud/macos`)
|
|
8
|
-
- FlutterMacOS (from `Flutter/ephemeral`)
|
|
9
|
-
|
|
10
|
-
EXTERNAL SOURCES:
|
|
11
|
-
flutter_soloud:
|
|
12
|
-
:path: Flutter/ephemeral/.symlinks/plugins/flutter_soloud/macos
|
|
13
|
-
FlutterMacOS:
|
|
14
|
-
:path: Flutter/ephemeral
|
|
15
|
-
|
|
16
|
-
SPEC CHECKSUMS:
|
|
17
|
-
flutter_soloud: 93c20641a100d25bfbc84a8b86e867f350ef29ac
|
|
18
|
-
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
|
19
|
-
|
|
20
|
-
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
|
|
21
|
-
|
|
22
|
-
COCOAPODS: 1.16.2
|
macos/Runner.xcodeproj/project.pbxproj
CHANGED
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
/* End PBXAggregateTarget section */
|
|
22
22
|
|
|
23
23
|
/* Begin PBXBuildFile section */
|
|
24
|
-
24975236CA37842BE288C1EF /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50275D2F47C1000366FA62E4 /* Pods_RunnerTests.framework */; };
|
|
25
24
|
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
|
|
26
25
|
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
|
|
27
26
|
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
|
|
@@ -29,7 +28,6 @@
|
|
|
29
28
|
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
|
|
30
29
|
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
|
|
31
30
|
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
|
|
32
|
-
A5F01B2318AAC442B3788873 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9C7846F35E82451F8353334E /* Pods_Runner.framework */; };
|
|
33
31
|
/* End PBXBuildFile section */
|
|
34
32
|
|
|
35
33
|
/* Begin PBXContainerItemProxy section */
|
|
@@ -66,7 +64,6 @@
|
|
|
66
64
|
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
67
65
|
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
|
68
66
|
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
|
|
69
|
-
334DCAC7AF1120FDF88EE10F /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
|
70
67
|
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
|
|
71
68
|
33CC10ED2044A3C60003C045 /* only_bible_app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = only_bible_app.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
72
69
|
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
|
@@ -80,16 +77,9 @@
|
|
|
80
77
|
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
|
|
81
78
|
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
|
|
82
79
|
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
|
|
83
|
-
35B9450ADD66E8BC4C4F3826 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
|
84
|
-
35CCE74F1920378D63AB4733 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
|
85
|
-
50275D2F47C1000366FA62E4 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
86
80
|
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
|
|
87
81
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
|
88
|
-
87E97379FB3EB86D1485488E /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
|
89
82
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
|
90
|
-
9C7846F35E82451F8353334E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
91
|
-
BFAE72D8B9900A8C04CAB06F /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
|
92
|
-
C6CC86F2F862899310B2073C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
|
93
83
|
/* End PBXFileReference section */
|
|
94
84
|
|
|
95
85
|
/* Begin PBXFrameworksBuildPhase section */
|
|
@@ -97,7 +87,6 @@
|
|
|
97
87
|
isa = PBXFrameworksBuildPhase;
|
|
98
88
|
buildActionMask = 2147483647;
|
|
99
89
|
files = (
|
|
100
|
-
24975236CA37842BE288C1EF /* Pods_RunnerTests.framework in Frameworks */,
|
|
101
90
|
);
|
|
102
91
|
runOnlyForDeploymentPostprocessing = 0;
|
|
103
92
|
};
|
|
@@ -106,7 +95,6 @@
|
|
|
106
95
|
buildActionMask = 2147483647;
|
|
107
96
|
files = (
|
|
108
97
|
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
|
|
109
|
-
A5F01B2318AAC442B3788873 /* Pods_Runner.framework in Frameworks */,
|
|
110
98
|
);
|
|
111
99
|
runOnlyForDeploymentPostprocessing = 0;
|
|
112
100
|
};
|
|
@@ -139,8 +127,6 @@
|
|
|
139
127
|
33CEB47122A05771004F2AC0 /* Flutter */,
|
|
140
128
|
331C80D6294CF71000263BE5 /* RunnerTests */,
|
|
141
129
|
33CC10EE2044A3C60003C045 /* Products */,
|
|
142
|
-
D73912EC22F37F3D000D13A0 /* Frameworks */,
|
|
143
|
-
386D91C7345CD19195A06092 /* Pods */,
|
|
144
130
|
);
|
|
145
131
|
sourceTree = "<group>";
|
|
146
132
|
};
|
|
@@ -189,29 +175,6 @@
|
|
|
189
175
|
path = Runner;
|
|
190
176
|
sourceTree = "<group>";
|
|
191
177
|
};
|
|
192
|
-
386D91C7345CD19195A06092 /* Pods */ = {
|
|
193
|
-
isa = PBXGroup;
|
|
194
|
-
children = (
|
|
195
|
-
C6CC86F2F862899310B2073C /* Pods-Runner.debug.xcconfig */,
|
|
196
|
-
35B9450ADD66E8BC4C4F3826 /* Pods-Runner.release.xcconfig */,
|
|
197
|
-
35CCE74F1920378D63AB4733 /* Pods-Runner.profile.xcconfig */,
|
|
198
|
-
334DCAC7AF1120FDF88EE10F /* Pods-RunnerTests.debug.xcconfig */,
|
|
199
|
-
87E97379FB3EB86D1485488E /* Pods-RunnerTests.release.xcconfig */,
|
|
200
|
-
BFAE72D8B9900A8C04CAB06F /* Pods-RunnerTests.profile.xcconfig */,
|
|
201
|
-
);
|
|
202
|
-
name = Pods;
|
|
203
|
-
path = Pods;
|
|
204
|
-
sourceTree = "<group>";
|
|
205
|
-
};
|
|
206
|
-
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
|
|
207
|
-
isa = PBXGroup;
|
|
208
|
-
children = (
|
|
209
|
-
9C7846F35E82451F8353334E /* Pods_Runner.framework */,
|
|
210
|
-
50275D2F47C1000366FA62E4 /* Pods_RunnerTests.framework */,
|
|
211
|
-
);
|
|
212
|
-
name = Frameworks;
|
|
213
|
-
sourceTree = "<group>";
|
|
214
|
-
};
|
|
215
178
|
/* End PBXGroup section */
|
|
216
179
|
|
|
217
180
|
/* Begin PBXNativeTarget section */
|
|
@@ -219,7 +182,6 @@
|
|
|
219
182
|
isa = PBXNativeTarget;
|
|
220
183
|
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
|
221
184
|
buildPhases = (
|
|
222
|
-
109FC7E83F5C961E0F9FC371 /* [CP] Check Pods Manifest.lock */,
|
|
223
185
|
331C80D1294CF70F00263BE5 /* Sources */,
|
|
224
186
|
331C80D2294CF70F00263BE5 /* Frameworks */,
|
|
225
187
|
331C80D3294CF70F00263BE5 /* Resources */,
|
|
@@ -238,13 +200,11 @@
|
|
|
238
200
|
isa = PBXNativeTarget;
|
|
239
201
|
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
|
|
240
202
|
buildPhases = (
|
|
241
|
-
2FD51143F0581615244DC8B0 /* [CP] Check Pods Manifest.lock */,
|
|
242
203
|
33CC10E92044A3C60003C045 /* Sources */,
|
|
243
204
|
33CC10EA2044A3C60003C045 /* Frameworks */,
|
|
244
205
|
33CC10EB2044A3C60003C045 /* Resources */,
|
|
245
206
|
33CC110E2044A8840003C045 /* Bundle Framework */,
|
|
246
207
|
3399D490228B24CF009A79C7 /* ShellScript */,
|
|
247
|
-
86A82F343DF3AEF021A46991 /* [CP] Embed Pods Frameworks */,
|
|
248
208
|
);
|
|
249
209
|
buildRules = (
|
|
250
210
|
);
|
|
@@ -333,50 +293,6 @@
|
|
|
333
293
|
/* End PBXResourcesBuildPhase section */
|
|
334
294
|
|
|
335
295
|
/* Begin PBXShellScriptBuildPhase section */
|
|
336
|
-
109FC7E83F5C961E0F9FC371 /* [CP] Check Pods Manifest.lock */ = {
|
|
337
|
-
isa = PBXShellScriptBuildPhase;
|
|
338
|
-
buildActionMask = 2147483647;
|
|
339
|
-
files = (
|
|
340
|
-
);
|
|
341
|
-
inputFileListPaths = (
|
|
342
|
-
);
|
|
343
|
-
inputPaths = (
|
|
344
|
-
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
|
345
|
-
"${PODS_ROOT}/Manifest.lock",
|
|
346
|
-
);
|
|
347
|
-
name = "[CP] Check Pods Manifest.lock";
|
|
348
|
-
outputFileListPaths = (
|
|
349
|
-
);
|
|
350
|
-
outputPaths = (
|
|
351
|
-
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
|
|
352
|
-
);
|
|
353
|
-
runOnlyForDeploymentPostprocessing = 0;
|
|
354
|
-
shellPath = /bin/sh;
|
|
355
|
-
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
|
356
|
-
showEnvVarsInLog = 0;
|
|
357
|
-
};
|
|
358
|
-
2FD51143F0581615244DC8B0 /* [CP] Check Pods Manifest.lock */ = {
|
|
359
|
-
isa = PBXShellScriptBuildPhase;
|
|
360
|
-
buildActionMask = 2147483647;
|
|
361
|
-
files = (
|
|
362
|
-
);
|
|
363
|
-
inputFileListPaths = (
|
|
364
|
-
);
|
|
365
|
-
inputPaths = (
|
|
366
|
-
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
|
367
|
-
"${PODS_ROOT}/Manifest.lock",
|
|
368
|
-
);
|
|
369
|
-
name = "[CP] Check Pods Manifest.lock";
|
|
370
|
-
outputFileListPaths = (
|
|
371
|
-
);
|
|
372
|
-
outputPaths = (
|
|
373
|
-
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
|
374
|
-
);
|
|
375
|
-
runOnlyForDeploymentPostprocessing = 0;
|
|
376
|
-
shellPath = /bin/sh;
|
|
377
|
-
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
|
378
|
-
showEnvVarsInLog = 0;
|
|
379
|
-
};
|
|
380
296
|
3399D490228B24CF009A79C7 /* ShellScript */ = {
|
|
381
297
|
isa = PBXShellScriptBuildPhase;
|
|
382
298
|
alwaysOutOfDate = 1;
|
|
@@ -415,23 +331,6 @@
|
|
|
415
331
|
shellPath = /bin/sh;
|
|
416
332
|
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
|
|
417
333
|
};
|
|
418
|
-
86A82F343DF3AEF021A46991 /* [CP] Embed Pods Frameworks */ = {
|
|
419
|
-
isa = PBXShellScriptBuildPhase;
|
|
420
|
-
buildActionMask = 2147483647;
|
|
421
|
-
files = (
|
|
422
|
-
);
|
|
423
|
-
inputFileListPaths = (
|
|
424
|
-
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
|
425
|
-
);
|
|
426
|
-
name = "[CP] Embed Pods Frameworks";
|
|
427
|
-
outputFileListPaths = (
|
|
428
|
-
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
|
429
|
-
);
|
|
430
|
-
runOnlyForDeploymentPostprocessing = 0;
|
|
431
|
-
shellPath = /bin/sh;
|
|
432
|
-
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
|
433
|
-
showEnvVarsInLog = 0;
|
|
434
|
-
};
|
|
435
334
|
/* End PBXShellScriptBuildPhase section */
|
|
436
335
|
|
|
437
336
|
/* Begin PBXSourcesBuildPhase section */
|
|
@@ -483,13 +382,12 @@
|
|
|
483
382
|
/* Begin XCBuildConfiguration section */
|
|
484
383
|
331C80DB294CF71000263BE5 /* Debug */ = {
|
|
485
384
|
isa = XCBuildConfiguration;
|
|
486
|
-
baseConfigurationReference = 334DCAC7AF1120FDF88EE10F /* Pods-RunnerTests.debug.xcconfig */;
|
|
487
385
|
buildSettings = {
|
|
488
386
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
|
489
387
|
CURRENT_PROJECT_VERSION = 5;
|
|
490
388
|
GENERATE_INFOPLIST_FILE = YES;
|
|
491
389
|
MARKETING_VERSION = 1.0;
|
|
492
|
-
PRODUCT_BUNDLE_IDENTIFIER = sh.pyros.only-bible-app.RunnerTests;
|
|
390
|
+
PRODUCT_BUNDLE_IDENTIFIER = "sh.pyros.only-bible-app.RunnerTests";
|
|
493
391
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
494
392
|
SWIFT_VERSION = 5.0;
|
|
495
393
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/only_bible_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/only_bible_app";
|
|
@@ -498,13 +396,12 @@
|
|
|
498
396
|
};
|
|
499
397
|
331C80DC294CF71000263BE5 /* Release */ = {
|
|
500
398
|
isa = XCBuildConfiguration;
|
|
501
|
-
baseConfigurationReference = 87E97379FB3EB86D1485488E /* Pods-RunnerTests.release.xcconfig */;
|
|
502
399
|
buildSettings = {
|
|
503
400
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
|
504
401
|
CURRENT_PROJECT_VERSION = 5;
|
|
505
402
|
GENERATE_INFOPLIST_FILE = YES;
|
|
506
403
|
MARKETING_VERSION = 1.0;
|
|
507
|
-
PRODUCT_BUNDLE_IDENTIFIER = sh.pyros.only-bible-app.RunnerTests;
|
|
404
|
+
PRODUCT_BUNDLE_IDENTIFIER = "sh.pyros.only-bible-app.RunnerTests";
|
|
508
405
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
509
406
|
SWIFT_VERSION = 5.0;
|
|
510
407
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/only_bible_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/only_bible_app";
|
|
@@ -513,13 +410,12 @@
|
|
|
513
410
|
};
|
|
514
411
|
331C80DD294CF71000263BE5 /* Profile */ = {
|
|
515
412
|
isa = XCBuildConfiguration;
|
|
516
|
-
baseConfigurationReference = BFAE72D8B9900A8C04CAB06F /* Pods-RunnerTests.profile.xcconfig */;
|
|
517
413
|
buildSettings = {
|
|
518
414
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
|
519
415
|
CURRENT_PROJECT_VERSION = 5;
|
|
520
416
|
GENERATE_INFOPLIST_FILE = YES;
|
|
521
417
|
MARKETING_VERSION = 1.0;
|
|
522
|
-
PRODUCT_BUNDLE_IDENTIFIER = sh.pyros.only-bible-app.RunnerTests;
|
|
418
|
+
PRODUCT_BUNDLE_IDENTIFIER = "sh.pyros.only-bible-app.RunnerTests";
|
|
523
419
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
524
420
|
SWIFT_VERSION = 5.0;
|
|
525
421
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/only_bible_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/only_bible_app";
|
macos/Runner.xcworkspace/contents.xcworkspacedata
CHANGED
|
@@ -4,7 +4,4 @@
|
|
|
4
4
|
<FileRef
|
|
5
5
|
location = "group:Runner.xcodeproj">
|
|
6
6
|
</FileRef>
|
|
7
|
-
<FileRef
|
|
8
|
-
location = "group:Pods/Pods.xcodeproj">
|
|
9
|
-
</FileRef>
|
|
10
7
|
</Workspace>
|
macos/Runner/Info.plist
CHANGED
|
@@ -32,9 +32,5 @@
|
|
|
32
32
|
<string>MainMenu</string>
|
|
33
33
|
<key>NSPrincipalClass</key>
|
|
34
34
|
<string>NSApplication</string>
|
|
35
|
-
<key>NSPhotoLibraryUsageDescription</key>
|
|
36
|
-
<string>Only Bible App uses the system file picker to let you choose a backup file to restore your highlights and settings. Depending on your device, this picker may show your photo library, though Only Bible App does not access your photos directly.</string>
|
|
37
|
-
<key>NSAppleMusicUsageDescription</key>
|
|
38
|
-
<string>Only Bible App does not access your music library. This description is required because a bundled library references media library APIs, even though this app does not use them.</string>
|
|
39
35
|
</dict>
|
|
40
36
|
</plist>
|
macos/fastlane/Fastfile
CHANGED
|
@@ -60,15 +60,16 @@ platform :mac do
|
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
desc "Push a new release build to the Mac App Store"
|
|
63
|
-
lane :release do
|
|
63
|
+
lane :release do |options|
|
|
64
|
-
screenshots
|
|
64
|
+
screenshots unless options[:skip_screenshots]
|
|
65
65
|
build
|
|
66
66
|
api_key = api_key()
|
|
67
67
|
upload_to_app_store(
|
|
68
68
|
api_key: api_key,
|
|
69
69
|
platform: "osx",
|
|
70
70
|
pkg: lane_context[SharedValues::PKG_OUTPUT_PATH],
|
|
71
|
-
overwrite_screenshots: true
|
|
71
|
+
overwrite_screenshots: true,
|
|
72
|
+
precheck_include_in_app_purchases: false
|
|
72
73
|
)
|
|
73
74
|
end
|
|
74
75
|
|
macos/fastlane/report.xml
CHANGED
|
@@ -5,32 +5,67 @@
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
<testcase classname="fastlane.lanes" name="
|
|
8
|
+
<testcase classname="fastlane.lanes" name="00: default_platform" time="0.000187">
|
|
9
9
|
|
|
10
10
|
</testcase>
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
<testcase classname="fastlane.lanes" name="
|
|
13
|
+
<testcase classname="fastlane.lanes" name="01: Switch to mac build lane" time="0.000122">
|
|
14
14
|
|
|
15
15
|
</testcase>
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
<testcase classname="fastlane.lanes" name="
|
|
18
|
+
<testcase classname="fastlane.lanes" name="02: Switch to mac certs lane" time="0.000138">
|
|
19
19
|
|
|
20
20
|
</testcase>
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
<testcase classname="fastlane.lanes" name="
|
|
23
|
+
<testcase classname="fastlane.lanes" name="03: Switch to mac api_key lane" time="7.1e-05">
|
|
24
24
|
|
|
25
25
|
</testcase>
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
<testcase classname="fastlane.lanes" name="
|
|
28
|
+
<testcase classname="fastlane.lanes" name="04: app_store_connect_api_key" time="0.003265">
|
|
29
29
|
|
|
30
30
|
</testcase>
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
<testcase classname="fastlane.lanes" name="
|
|
33
|
+
<testcase classname="fastlane.lanes" name="05: cert" time="1.472711">
|
|
34
|
+
|
|
35
|
+
</testcase>
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
<testcase classname="fastlane.lanes" name="06: sigh" time="1.169831">
|
|
39
|
+
|
|
40
|
+
</testcase>
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
<testcase classname="fastlane.lanes" name="07: cert" time="1.869459">
|
|
44
|
+
|
|
45
|
+
</testcase>
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
<testcase classname="fastlane.lanes" name="08: cd ../.. && flutter build macos --config-only" time="7.146192">
|
|
49
|
+
|
|
50
|
+
</testcase>
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
<testcase classname="fastlane.lanes" name="09: build_mac_app" time="86.815746">
|
|
54
|
+
|
|
55
|
+
</testcase>
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
<testcase classname="fastlane.lanes" name="10: Switch to mac api_key lane" time="0.000101">
|
|
59
|
+
|
|
60
|
+
</testcase>
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
<testcase classname="fastlane.lanes" name="11: app_store_connect_api_key" time="0.00169">
|
|
64
|
+
|
|
65
|
+
</testcase>
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
<testcase classname="fastlane.lanes" name="12: upload_to_app_store" time="481.245784">
|
|
34
69
|
|
|
35
70
|
</testcase>
|
|
36
71
|
|
pubspec.lock
CHANGED
|
@@ -65,22 +65,62 @@ packages:
|
|
|
65
65
|
url: "https://pub.dev"
|
|
66
66
|
source: hosted
|
|
67
67
|
version: "1.5.0"
|
|
68
|
+
audioplayers:
|
|
69
|
+
dependency: "direct main"
|
|
68
|
-
|
|
70
|
+
description:
|
|
71
|
+
name: audioplayers
|
|
72
|
+
sha256: "2ba4bb2944baacbdd5372ff8254a8e7feb8c10d7739545e392f5605a8f618745"
|
|
73
|
+
url: "https://pub.dev"
|
|
74
|
+
source: hosted
|
|
75
|
+
version: "6.8.1"
|
|
76
|
+
audioplayers_android:
|
|
77
|
+
dependency: transitive
|
|
78
|
+
description:
|
|
79
|
+
name: audioplayers_android
|
|
80
|
+
sha256: f5ff5b15620fbab8cb0849e9636c48e2b96c3f0f71723bbbe2ad3c761b205f05
|
|
81
|
+
url: "https://pub.dev"
|
|
82
|
+
source: hosted
|
|
83
|
+
version: "5.3.0"
|
|
84
|
+
audioplayers_darwin:
|
|
85
|
+
dependency: transitive
|
|
86
|
+
description:
|
|
87
|
+
name: audioplayers_darwin
|
|
88
|
+
sha256: "1ca553add991384ecf421b9569da850f3ab2472ffb83f6970b0416365abc51be"
|
|
89
|
+
url: "https://pub.dev"
|
|
90
|
+
source: hosted
|
|
91
|
+
version: "6.5.0"
|
|
92
|
+
audioplayers_linux:
|
|
93
|
+
dependency: transitive
|
|
94
|
+
description:
|
|
95
|
+
name: audioplayers_linux
|
|
96
|
+
sha256: "15178b726b7cdee5364d0463c8d445630c4e0fb7d26612b73c767e7d25de9417"
|
|
97
|
+
url: "https://pub.dev"
|
|
98
|
+
source: hosted
|
|
99
|
+
version: "4.3.0"
|
|
100
|
+
audioplayers_platform_interface:
|
|
69
101
|
dependency: transitive
|
|
70
102
|
description:
|
|
71
|
-
name:
|
|
103
|
+
name: audioplayers_platform_interface
|
|
72
|
-
sha256: "
|
|
104
|
+
sha256: "765f6f0e6dca55cb471c9483fc77700564b3484d19198aca4ebb5147c6c85acb"
|
|
73
105
|
url: "https://pub.dev"
|
|
74
106
|
source: hosted
|
|
75
|
-
version: "
|
|
107
|
+
version: "7.2.0"
|
|
76
|
-
|
|
108
|
+
audioplayers_web:
|
|
77
109
|
dependency: transitive
|
|
78
110
|
description:
|
|
79
|
-
name:
|
|
111
|
+
name: audioplayers_web
|
|
80
|
-
sha256:
|
|
112
|
+
sha256: ae1e0103c865a03e273f6d13d97b93f5595eac09915729cd5e37ef96e2857319
|
|
81
113
|
url: "https://pub.dev"
|
|
82
114
|
source: hosted
|
|
115
|
+
version: "5.3.0"
|
|
116
|
+
audioplayers_windows:
|
|
117
|
+
dependency: transitive
|
|
118
|
+
description:
|
|
119
|
+
name: audioplayers_windows
|
|
120
|
+
sha256: a70ae82bba2dfcb6eb03dd4815d737a2d46d33ea5a96a03f535cfcaac490e413
|
|
121
|
+
url: "https://pub.dev"
|
|
122
|
+
source: hosted
|
|
83
|
-
version: "
|
|
123
|
+
version: "4.4.1"
|
|
84
124
|
boolean_selector:
|
|
85
125
|
dependency: transitive
|
|
86
126
|
description:
|
|
@@ -353,14 +393,70 @@ packages:
|
|
|
353
393
|
url: "https://pub.dev"
|
|
354
394
|
source: hosted
|
|
355
395
|
version: "7.0.1"
|
|
356
|
-
|
|
396
|
+
file_selector:
|
|
357
397
|
dependency: "direct main"
|
|
358
398
|
description:
|
|
359
|
-
name:
|
|
399
|
+
name: file_selector
|
|
400
|
+
sha256: bd15e43e9268db636b53eeaca9f56324d1622af30e5c34d6e267649758c84d9a
|
|
401
|
+
url: "https://pub.dev"
|
|
402
|
+
source: hosted
|
|
403
|
+
version: "1.1.0"
|
|
404
|
+
file_selector_android:
|
|
405
|
+
dependency: transitive
|
|
406
|
+
description:
|
|
407
|
+
name: file_selector_android
|
|
408
|
+
sha256: "1d45e9910f68c16eb0c74f0b10097ad81aed516ea28054c027137e8f7d75e840"
|
|
409
|
+
url: "https://pub.dev"
|
|
410
|
+
source: hosted
|
|
411
|
+
version: "0.5.2+9"
|
|
412
|
+
file_selector_ios:
|
|
413
|
+
dependency: transitive
|
|
414
|
+
description:
|
|
415
|
+
name: file_selector_ios
|
|
416
|
+
sha256: e2ecf2885c121691ce13b60db3508f53c01f869fb6e8dc5c1cfa771e4c46aeca
|
|
417
|
+
url: "https://pub.dev"
|
|
418
|
+
source: hosted
|
|
419
|
+
version: "0.5.3+5"
|
|
420
|
+
file_selector_linux:
|
|
421
|
+
dependency: transitive
|
|
422
|
+
description:
|
|
423
|
+
name: file_selector_linux
|
|
424
|
+
sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0"
|
|
425
|
+
url: "https://pub.dev"
|
|
426
|
+
source: hosted
|
|
427
|
+
version: "0.9.4"
|
|
428
|
+
file_selector_macos:
|
|
429
|
+
dependency: transitive
|
|
430
|
+
description:
|
|
431
|
+
name: file_selector_macos
|
|
432
|
+
sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a"
|
|
433
|
+
url: "https://pub.dev"
|
|
434
|
+
source: hosted
|
|
435
|
+
version: "0.9.5"
|
|
436
|
+
file_selector_platform_interface:
|
|
437
|
+
dependency: transitive
|
|
438
|
+
description:
|
|
439
|
+
name: file_selector_platform_interface
|
|
440
|
+
sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85"
|
|
441
|
+
url: "https://pub.dev"
|
|
442
|
+
source: hosted
|
|
443
|
+
version: "2.7.0"
|
|
444
|
+
file_selector_web:
|
|
445
|
+
dependency: transitive
|
|
446
|
+
description:
|
|
447
|
+
name: file_selector_web
|
|
360
|
-
sha256: "
|
|
448
|
+
sha256: "73181fbc5257776d8ecaa6a94ab3c8e920ad143b9132a6d984a9271dfc6928d3"
|
|
449
|
+
url: "https://pub.dev"
|
|
450
|
+
source: hosted
|
|
451
|
+
version: "0.9.5"
|
|
452
|
+
file_selector_windows:
|
|
453
|
+
dependency: transitive
|
|
454
|
+
description:
|
|
455
|
+
name: file_selector_windows
|
|
456
|
+
sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd"
|
|
361
457
|
url: "https://pub.dev"
|
|
362
458
|
source: hosted
|
|
363
|
-
version: "
|
|
459
|
+
version: "0.9.3+5"
|
|
364
460
|
fixnum:
|
|
365
461
|
dependency: transitive
|
|
366
462
|
description:
|
|
@@ -390,14 +486,6 @@ packages:
|
|
|
390
486
|
url: "https://pub.dev"
|
|
391
487
|
source: hosted
|
|
392
488
|
version: "1.0.0"
|
|
393
|
-
flutter_cache_manager:
|
|
394
|
-
dependency: transitive
|
|
395
|
-
description:
|
|
396
|
-
name: flutter_cache_manager
|
|
397
|
-
sha256: "1de7849213b4c73c85aca7e0ac687a9a5d82ccdb594366b9dcc26cb6a2189cd2"
|
|
398
|
-
url: "https://pub.dev"
|
|
399
|
-
source: hosted
|
|
400
|
-
version: "3.4.2"
|
|
401
489
|
flutter_driver:
|
|
402
490
|
dependency: transitive
|
|
403
491
|
description: flutter
|
|
@@ -443,70 +531,6 @@ packages:
|
|
|
443
531
|
url: "https://pub.dev"
|
|
444
532
|
source: hosted
|
|
445
533
|
version: "2.4.8"
|
|
446
|
-
flutter_plugin_android_lifecycle:
|
|
447
|
-
dependency: transitive
|
|
448
|
-
description:
|
|
449
|
-
name: flutter_plugin_android_lifecycle
|
|
450
|
-
sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785"
|
|
451
|
-
url: "https://pub.dev"
|
|
452
|
-
source: hosted
|
|
453
|
-
version: "2.0.35"
|
|
454
|
-
flutter_secure_storage:
|
|
455
|
-
dependency: transitive
|
|
456
|
-
description:
|
|
457
|
-
name: flutter_secure_storage
|
|
458
|
-
sha256: "7686b1d6a29985dcbb808c59518226e603e3bfa7c0ddfd1a0d00e4cda77c868e"
|
|
459
|
-
url: "https://pub.dev"
|
|
460
|
-
source: hosted
|
|
461
|
-
version: "10.3.1"
|
|
462
|
-
flutter_secure_storage_darwin:
|
|
463
|
-
dependency: transitive
|
|
464
|
-
description:
|
|
465
|
-
name: flutter_secure_storage_darwin
|
|
466
|
-
sha256: "82329fa5cdf343773b1b6897dea959105a29f092454259edff92f9f6637e8149"
|
|
467
|
-
url: "https://pub.dev"
|
|
468
|
-
source: hosted
|
|
469
|
-
version: "0.3.2"
|
|
470
|
-
flutter_secure_storage_linux:
|
|
471
|
-
dependency: transitive
|
|
472
|
-
description:
|
|
473
|
-
name: flutter_secure_storage_linux
|
|
474
|
-
sha256: a5f35ddab43cf5c8215d2feb4ce1957851f28c5c37e6f04335066a0602087bf5
|
|
475
|
-
url: "https://pub.dev"
|
|
476
|
-
source: hosted
|
|
477
|
-
version: "3.0.1"
|
|
478
|
-
flutter_secure_storage_platform_interface:
|
|
479
|
-
dependency: transitive
|
|
480
|
-
description:
|
|
481
|
-
name: flutter_secure_storage_platform_interface
|
|
482
|
-
sha256: "06686df417f34fe9f963ed217b7fdce250e2f637ddd6c374d7eb054549770633"
|
|
483
|
-
url: "https://pub.dev"
|
|
484
|
-
source: hosted
|
|
485
|
-
version: "2.0.2"
|
|
486
|
-
flutter_secure_storage_web:
|
|
487
|
-
dependency: transitive
|
|
488
|
-
description:
|
|
489
|
-
name: flutter_secure_storage_web
|
|
490
|
-
sha256: "073a62b3aeb866ab4ce795f960413948e51e5a42a9b0c8333b6daf5bb3208a1c"
|
|
491
|
-
url: "https://pub.dev"
|
|
492
|
-
source: hosted
|
|
493
|
-
version: "2.1.1"
|
|
494
|
-
flutter_secure_storage_windows:
|
|
495
|
-
dependency: transitive
|
|
496
|
-
description:
|
|
497
|
-
name: flutter_secure_storage_windows
|
|
498
|
-
sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613"
|
|
499
|
-
url: "https://pub.dev"
|
|
500
|
-
source: hosted
|
|
501
|
-
version: "4.1.0"
|
|
502
|
-
flutter_soloud:
|
|
503
|
-
dependency: "direct main"
|
|
504
|
-
description:
|
|
505
|
-
name: flutter_soloud
|
|
506
|
-
sha256: d275ff1fcaca64eab81189f260613b42787655d213013ab316ecd31bb52a5913
|
|
507
|
-
url: "https://pub.dev"
|
|
508
|
-
source: hosted
|
|
509
|
-
version: "3.5.4"
|
|
510
534
|
flutter_test:
|
|
511
535
|
dependency: "direct dev"
|
|
512
536
|
description: flutter
|
|
@@ -594,14 +618,6 @@ packages:
|
|
|
594
618
|
url: "https://pub.dev"
|
|
595
619
|
source: hosted
|
|
596
620
|
version: "1.6.0"
|
|
597
|
-
http2:
|
|
598
|
-
dependency: transitive
|
|
599
|
-
description:
|
|
600
|
-
name: http2
|
|
601
|
-
sha256: "382d3aefc5bd6dc68c6b892d7664f29b5beb3251611ae946a98d35158a82bbfa"
|
|
602
|
-
url: "https://pub.dev"
|
|
603
|
-
source: hosted
|
|
604
|
-
version: "2.3.1"
|
|
605
621
|
http_multi_server:
|
|
606
622
|
dependency: transitive
|
|
607
623
|
description:
|
|
@@ -783,14 +799,6 @@ packages:
|
|
|
783
799
|
url: "https://pub.dev"
|
|
784
800
|
source: hosted
|
|
785
801
|
version: "9.5.0"
|
|
786
|
-
os_detect:
|
|
787
|
-
dependency: transitive
|
|
788
|
-
description:
|
|
789
|
-
name: os_detect
|
|
790
|
-
sha256: "7d87c0dd98c6faf110d5aa498e9a6df02ffce4bb78cc9cfc8ad02929be9bb71f"
|
|
791
|
-
url: "https://pub.dev"
|
|
792
|
-
source: hosted
|
|
793
|
-
version: "2.0.3"
|
|
794
802
|
package_config:
|
|
795
803
|
dependency: transitive
|
|
796
804
|
description:
|
|
@@ -959,14 +967,6 @@ packages:
|
|
|
959
967
|
url: "https://pub.dev"
|
|
960
968
|
source: hosted
|
|
961
969
|
version: "0.6.0"
|
|
962
|
-
rxdart:
|
|
963
|
-
dependency: transitive
|
|
964
|
-
description:
|
|
965
|
-
name: rxdart
|
|
966
|
-
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
|
|
967
|
-
url: "https://pub.dev"
|
|
968
|
-
source: hosted
|
|
969
|
-
version: "0.28.0"
|
|
970
970
|
serverpod_serialization:
|
|
971
971
|
dependency: transitive
|
|
972
972
|
description:
|
|
@@ -991,13 +991,6 @@ packages:
|
|
|
991
991
|
url: "https://pub.dev"
|
|
992
992
|
source: hosted
|
|
993
993
|
version: "6.1.0"
|
|
994
|
-
shared:
|
|
995
|
-
dependency: "direct main"
|
|
996
|
-
description:
|
|
997
|
-
path: "../r2-backed-apps/packages/shared"
|
|
998
|
-
relative: true
|
|
999
|
-
source: path
|
|
1000
|
-
version: "0.0.1"
|
|
1001
994
|
shelf:
|
|
1002
995
|
dependency: transitive
|
|
1003
996
|
description:
|
|
@@ -1075,46 +1068,6 @@ packages:
|
|
|
1075
1068
|
url: "https://pub.dev"
|
|
1076
1069
|
source: hosted
|
|
1077
1070
|
version: "7.0.0"
|
|
1078
|
-
sqflite:
|
|
1079
|
-
dependency: transitive
|
|
1080
|
-
description:
|
|
1081
|
-
name: sqflite
|
|
1082
|
-
sha256: "58a799e6ac17dd32fbab93813d39ed835a75ccc0f8f85b8955fe318c6712b082"
|
|
1083
|
-
url: "https://pub.dev"
|
|
1084
|
-
source: hosted
|
|
1085
|
-
version: "2.4.3"
|
|
1086
|
-
sqflite_android:
|
|
1087
|
-
dependency: transitive
|
|
1088
|
-
description:
|
|
1089
|
-
name: sqflite_android
|
|
1090
|
-
sha256: d0548f9d7422a2dae99ec6f8b0a3074463b132d216fa5ba0d230eeefc901983b
|
|
1091
|
-
url: "https://pub.dev"
|
|
1092
|
-
source: hosted
|
|
1093
|
-
version: "2.4.3"
|
|
1094
|
-
sqflite_common:
|
|
1095
|
-
dependency: transitive
|
|
1096
|
-
description:
|
|
1097
|
-
name: sqflite_common
|
|
1098
|
-
sha256: "5bf6a55c166e73bf651ba7ec3ed486e577620e3dc8f3a9c6a258a8031b624590"
|
|
1099
|
-
url: "https://pub.dev"
|
|
1100
|
-
source: hosted
|
|
1101
|
-
version: "2.5.11"
|
|
1102
|
-
sqflite_darwin:
|
|
1103
|
-
dependency: transitive
|
|
1104
|
-
description:
|
|
1105
|
-
name: sqflite_darwin
|
|
1106
|
-
sha256: c86ca18b8f666bbf903924687fe21cc16fc385d086005067e26619ca530bef9f
|
|
1107
|
-
url: "https://pub.dev"
|
|
1108
|
-
source: hosted
|
|
1109
|
-
version: "2.4.3+1"
|
|
1110
|
-
sqflite_platform_interface:
|
|
1111
|
-
dependency: transitive
|
|
1112
|
-
description:
|
|
1113
|
-
name: sqflite_platform_interface
|
|
1114
|
-
sha256: f84939f84350d92d04416f8bc4dc52d3896aec7716cc9e80cf0146342139dc50
|
|
1115
|
-
url: "https://pub.dev"
|
|
1116
|
-
source: hosted
|
|
1117
|
-
version: "2.4.1"
|
|
1118
1071
|
stack_trace:
|
|
1119
1072
|
dependency: transitive
|
|
1120
1073
|
description:
|
pubspec.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
name: only_bible_app
|
|
2
2
|
description: The only bible app you will ever need
|
|
3
3
|
publish_to: 'none'
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.7.0+23
|
|
5
5
|
|
|
6
6
|
environment:
|
|
7
7
|
sdk: '^3.12.2'
|
|
@@ -18,13 +18,11 @@ dependencies:
|
|
|
18
18
|
async_redux: ^28.0.0
|
|
19
19
|
flat_buffers: ^25.9.23
|
|
20
20
|
go_router: ^17.1.0
|
|
21
|
-
flutter_soloud: ^3.5.4
|
|
22
21
|
archive: ^4.0.9
|
|
23
22
|
envied: ^1.3.8
|
|
24
|
-
file_picker: ^11.0.2
|
|
25
23
|
flutter_azure_tts: ^1.0.0
|
|
26
|
-
|
|
24
|
+
audioplayers: ^6.8.1
|
|
27
|
-
|
|
25
|
+
file_selector: ^1.1.0
|
|
28
26
|
|
|
29
27
|
dev_dependencies:
|
|
30
28
|
flutter_test:
|
readme.md
CHANGED
|
@@ -67,60 +67,18 @@ signing details; iOS/macOS certs and provisioning profiles are fetched automatic
|
|
|
67
67
|
For iOS/macOS, `overwrite_screenshots: true` is set so App Store Connect's existing
|
|
68
68
|
screenshots are always replaced rather than skipped as duplicates.
|
|
69
69
|
|
|
70
|
-
###
|
|
70
|
+
### All platforms at once
|
|
71
71
|
|
|
72
72
|
```
|
|
73
|
-
|
|
73
|
+
make release
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
Regenerates screenshots once, then builds, uploads and submits the appbundle to
|
|
77
|
-
|
|
77
|
+
the Play Store, the ipa to the App Store, and the pkg to the Mac App Store,
|
|
78
|
+
one after another. Equivalent to running `make release-android`,
|
|
79
|
+
`make release-ios` and `make release-macos` in sequence — use those
|
|
80
|
+
individually to release just one platform without the others.
|
|
78
81
|
|
|
79
|
-
To upload an appbundle you've already built instead:
|
|
80
|
-
|
|
81
|
-
```
|
|
82
|
-
flutter build appbundle --release
|
|
83
|
-
cd android; fastlane upload
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### iOS
|
|
87
|
-
|
|
88
|
-
Certificates and the App Store provisioning profile are created/reused automatically via `fastlane certs` (runs `cert` + `sigh` against App Store Connect), so no manual Xcode Signing tab setup is needed.
|
|
89
|
-
|
|
90
|
-
```
|
|
91
|
-
cd ios; fastlane release
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
`fastlane release` regenerates screenshots, ensures signing certs/provisioning profile, builds and submits to App Store Connect in one step.
|
|
95
|
-
|
|
96
|
-
To upload an ipa you've already built instead:
|
|
97
|
-
|
|
98
|
-
```
|
|
99
|
-
flutter build ipa --release
|
|
100
|
-
cd ios; fastlane upload
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
### macOS
|
|
104
|
-
|
|
105
|
-
Certificates and the App Store provisioning profile are created/reused automatically via `fastlane certs` (runs `cert` + `sigh` against App Store Connect), so no manual Xcode Signing tab setup is needed.
|
|
106
|
-
|
|
107
|
-
```
|
|
108
|
-
cd macos; fastlane release
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
`fastlane release` regenerates screenshots, ensures signing certs/provisioning profile, builds, then uploads to App Store Connect and submits for review.
|
|
112
|
-
|
|
113
|
-
To build a signed `only-bible-app.pkg` without uploading it:
|
|
114
|
-
|
|
115
|
-
```
|
|
116
|
-
cd macos; fastlane build
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
To upload a package you've already built (via `fastlane build` or Xcode) instead:
|
|
120
|
-
|
|
121
|
-
```
|
|
122
|
-
cd macos; fastlane upload
|
|
123
|
-
```
|
|
124
82
|
|
|
125
83
|
# Data Backup
|
|
126
84
|
|
test/app_logic_test.dart
CHANGED
|
@@ -3,6 +3,7 @@ import "dart:io";
|
|
|
3
3
|
import "package:async_redux/async_redux.dart";
|
|
4
4
|
import "package:flutter/material.dart";
|
|
5
5
|
import "package:flutter_test/flutter_test.dart";
|
|
6
|
+
import "package:go_router/go_router.dart";
|
|
6
7
|
import "package:path_provider_platform_interface/path_provider_platform_interface.dart";
|
|
7
8
|
import "package:only_bible_app/env.dart";
|
|
8
9
|
import "package:only_bible_app/gen/bible.gen.dart";
|
|
@@ -10,7 +11,7 @@ import "package:only_bible_app/store/actions_navigation.dart";
|
|
|
10
11
|
import "package:only_bible_app/store/actions_state.dart";
|
|
11
12
|
import "package:only_bible_app/store/app_persistor.dart";
|
|
12
13
|
import "package:only_bible_app/store/app_state.dart";
|
|
13
|
-
import "package:
|
|
14
|
+
import "package:only_bible_app/theme.dart";
|
|
14
15
|
import "package:only_bible_app/utils.dart";
|
|
15
16
|
|
|
16
17
|
/// A tiny 3-book Bible used to exercise book/chapter boundary logic without
|
|
@@ -37,15 +38,30 @@ Bible buildTestBible() {
|
|
|
37
38
|
index: 0,
|
|
38
39
|
book: 0,
|
|
39
40
|
verses: [
|
|
41
|
+
VerseObjectBuilder(
|
|
42
|
+
index: 0,
|
|
43
|
+
book: 0,
|
|
44
|
+
chapter: 0,
|
|
40
|
-
|
|
45
|
+
text: "In the beginning...",
|
|
46
|
+
),
|
|
47
|
+
VerseObjectBuilder(
|
|
48
|
+
index: 1,
|
|
49
|
+
book: 0,
|
|
50
|
+
chapter: 0,
|
|
41
|
-
|
|
51
|
+
text: "And the earth...",
|
|
52
|
+
),
|
|
42
53
|
],
|
|
43
54
|
),
|
|
44
55
|
ChapterObjectBuilder(
|
|
45
56
|
index: 1,
|
|
46
57
|
book: 0,
|
|
47
58
|
verses: [
|
|
59
|
+
VerseObjectBuilder(
|
|
60
|
+
index: 0,
|
|
61
|
+
book: 0,
|
|
62
|
+
chapter: 1,
|
|
48
|
-
|
|
63
|
+
text: "Chapter two verse one",
|
|
64
|
+
),
|
|
49
65
|
],
|
|
50
66
|
),
|
|
51
67
|
],
|
|
@@ -58,7 +74,12 @@ Bible buildTestBible() {
|
|
|
58
74
|
index: 0,
|
|
59
75
|
book: 1,
|
|
60
76
|
verses: [
|
|
77
|
+
VerseObjectBuilder(
|
|
78
|
+
index: 0,
|
|
79
|
+
book: 1,
|
|
80
|
+
chapter: 0,
|
|
61
|
-
|
|
81
|
+
text: "Judges chapter one verse one",
|
|
82
|
+
),
|
|
62
83
|
],
|
|
63
84
|
),
|
|
64
85
|
],
|
|
@@ -70,12 +91,26 @@ Bible buildTestBible() {
|
|
|
70
91
|
ChapterObjectBuilder(
|
|
71
92
|
index: 0,
|
|
72
93
|
book: 2,
|
|
94
|
+
verses: [
|
|
95
|
+
VerseObjectBuilder(
|
|
96
|
+
index: 0,
|
|
97
|
+
book: 2,
|
|
98
|
+
chapter: 0,
|
|
73
|
-
|
|
99
|
+
text: "Rev 1:1",
|
|
100
|
+
),
|
|
101
|
+
],
|
|
74
102
|
),
|
|
75
103
|
ChapterObjectBuilder(
|
|
76
104
|
index: 1,
|
|
77
105
|
book: 2,
|
|
106
|
+
verses: [
|
|
107
|
+
VerseObjectBuilder(
|
|
108
|
+
index: 0,
|
|
109
|
+
book: 2,
|
|
110
|
+
chapter: 1,
|
|
78
|
-
|
|
111
|
+
text: "Rev 2:1",
|
|
112
|
+
),
|
|
113
|
+
],
|
|
79
114
|
),
|
|
80
115
|
],
|
|
81
116
|
),
|
|
@@ -84,6 +119,20 @@ Bible buildTestBible() {
|
|
|
84
119
|
return Bible(builder.toBytes());
|
|
85
120
|
}
|
|
86
121
|
|
|
122
|
+
/// A minimal router matching app.dart's real "/chapter/:bookIndex/:chapterIndex"
|
|
123
|
+
/// route shape, for exercising navigation actions that call `router.push`/`pop`.
|
|
124
|
+
GoRouter buildTestRouter() {
|
|
125
|
+
return GoRouter(
|
|
126
|
+
initialLocation: "/chapter/0/0",
|
|
127
|
+
routes: [
|
|
128
|
+
GoRoute(
|
|
129
|
+
path: "/chapter/:bookIndex/:chapterIndex",
|
|
130
|
+
builder: (context, state) => const SizedBox(),
|
|
131
|
+
),
|
|
132
|
+
],
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
87
136
|
class _FakePathProviderPlatform extends PathProviderPlatform {
|
|
88
137
|
_FakePathProviderPlatform(this.path);
|
|
89
138
|
final String path;
|
|
@@ -124,26 +173,35 @@ void main() {
|
|
|
124
173
|
});
|
|
125
174
|
|
|
126
175
|
group("safe bible accessors", () {
|
|
176
|
+
test(
|
|
127
|
-
|
|
177
|
+
"bookAt returns null instead of throwing for an out-of-range index",
|
|
178
|
+
() {
|
|
128
|
-
|
|
179
|
+
final bible = buildTestBible();
|
|
129
|
-
|
|
180
|
+
expect(bible.bookAt(-1), isNull);
|
|
130
|
-
|
|
181
|
+
expect(bible.bookAt(99), isNull);
|
|
131
|
-
|
|
182
|
+
expect(bible.bookAt(1)?.name, "Judges");
|
|
183
|
+
},
|
|
132
|
-
|
|
184
|
+
);
|
|
185
|
+
|
|
133
|
-
|
|
186
|
+
test(
|
|
134
|
-
|
|
187
|
+
"chapterAt returns null instead of throwing for an out-of-range index",
|
|
188
|
+
() {
|
|
135
|
-
|
|
189
|
+
final genesis = buildTestBible().bookAt(0)!;
|
|
136
|
-
|
|
190
|
+
expect(genesis.chapterAt(-1), isNull);
|
|
137
|
-
|
|
191
|
+
expect(genesis.chapterAt(99), isNull);
|
|
138
|
-
|
|
192
|
+
expect(genesis.chapterAt(1)?.index, 1);
|
|
193
|
+
},
|
|
139
|
-
|
|
194
|
+
);
|
|
195
|
+
|
|
140
|
-
|
|
196
|
+
test(
|
|
141
|
-
|
|
197
|
+
"verseAt returns null instead of throwing for an out-of-range index",
|
|
198
|
+
() {
|
|
142
|
-
|
|
199
|
+
final chapter = buildTestBible().bookAt(0)!.chapterAt(0)!;
|
|
143
|
-
|
|
200
|
+
expect(chapter.verseAt(-1), isNull);
|
|
144
|
-
|
|
201
|
+
expect(chapter.verseAt(99), isNull);
|
|
145
|
-
|
|
202
|
+
expect(chapter.verseAt(1)?.text, "And the earth...");
|
|
203
|
+
},
|
|
146
|
-
|
|
204
|
+
);
|
|
147
205
|
});
|
|
148
206
|
|
|
149
207
|
group("Book.shortName", () {
|
|
@@ -155,13 +213,19 @@ void main() {
|
|
|
155
213
|
test("disambiguates Judges vs Jude", () {
|
|
156
214
|
expect(anyBook.shortName("Judges"), "Jdg");
|
|
157
215
|
expect(anyBook.shortName("Jude"), "Jud");
|
|
216
|
+
expect(
|
|
217
|
+
anyBook.shortName("Judges"),
|
|
158
|
-
|
|
218
|
+
isNot(equals(anyBook.shortName("Jude"))),
|
|
219
|
+
);
|
|
159
220
|
});
|
|
160
221
|
|
|
161
222
|
test("disambiguates Philemon vs Philippians", () {
|
|
162
223
|
expect(anyBook.shortName("Philemon"), "Phm");
|
|
163
224
|
expect(anyBook.shortName("Philippians"), "Php");
|
|
225
|
+
expect(
|
|
226
|
+
anyBook.shortName("Philemon"),
|
|
164
|
-
|
|
227
|
+
isNot(equals(anyBook.shortName("Philippians"))),
|
|
228
|
+
);
|
|
165
229
|
});
|
|
166
230
|
|
|
167
231
|
test("does not throw for 3-letter book names", () {
|
|
@@ -172,7 +236,10 @@ void main() {
|
|
|
172
236
|
test("keeps numbered-book abbreviations distinct", () {
|
|
173
237
|
expect(anyBook.shortName("1 Samuel"), "1Sa");
|
|
174
238
|
expect(anyBook.shortName("2 Samuel"), "2Sa");
|
|
239
|
+
expect(
|
|
240
|
+
anyBook.shortName("1 Timothy"),
|
|
175
|
-
|
|
241
|
+
isNot(equals(anyBook.shortName("1 Thessalonians"))),
|
|
242
|
+
);
|
|
176
243
|
});
|
|
177
244
|
});
|
|
178
245
|
|
|
@@ -186,7 +253,13 @@ void main() {
|
|
|
186
253
|
fontSize: 20,
|
|
187
254
|
themeMode: ThemeMode.dark,
|
|
188
255
|
highlightHistory: [
|
|
256
|
+
HighlightHistoryEntry(
|
|
257
|
+
book: 0,
|
|
258
|
+
chapter: 0,
|
|
259
|
+
verseIndex: 0,
|
|
260
|
+
colorIndex: 1,
|
|
189
|
-
|
|
261
|
+
timestamp: DateTime.utc(2026, 1, 1),
|
|
262
|
+
),
|
|
190
263
|
],
|
|
191
264
|
);
|
|
192
265
|
final restored = AppState.fromJson(state.toJson(), bible);
|
|
@@ -197,12 +270,18 @@ void main() {
|
|
|
197
270
|
expect(restored.highlightHistory.single.book, 0);
|
|
198
271
|
});
|
|
199
272
|
|
|
273
|
+
test(
|
|
200
|
-
|
|
274
|
+
"clamps an out-of-range persisted savedBook/savedChapter instead of crashing",
|
|
275
|
+
() {
|
|
201
|
-
|
|
276
|
+
final bible = buildTestBible();
|
|
202
|
-
|
|
277
|
+
final state = AppState.fromJson({
|
|
278
|
+
"savedBook": 999,
|
|
279
|
+
"savedChapter": 999,
|
|
280
|
+
}, bible);
|
|
203
|
-
|
|
281
|
+
expect(state.savedBook, 2);
|
|
204
|
-
|
|
282
|
+
expect(state.savedChapter, 1);
|
|
283
|
+
},
|
|
205
|
-
|
|
284
|
+
);
|
|
206
285
|
|
|
207
286
|
test("defaults missing fields", () {
|
|
208
287
|
final bible = buildTestBible();
|
|
@@ -215,21 +294,24 @@ void main() {
|
|
|
215
294
|
});
|
|
216
295
|
|
|
217
296
|
group("SelectVerseAction", () {
|
|
297
|
+
test(
|
|
218
|
-
|
|
298
|
+
"deselecting a verse only removes the matching book+chapter+index",
|
|
299
|
+
() async {
|
|
219
|
-
|
|
300
|
+
final bible = buildTestBible();
|
|
220
|
-
|
|
301
|
+
// Same verse index (0) in two different chapters of the same book -
|
|
221
|
-
|
|
302
|
+
// exactly the collision the deselect-path bug missed.
|
|
222
|
-
|
|
303
|
+
final verseInChapter0 = bible.bookAt(0)!.chapterAt(0)!.verseAt(0)!;
|
|
223
|
-
|
|
304
|
+
final verseInChapter1 = bible.bookAt(0)!.chapterAt(1)!.verseAt(0)!;
|
|
224
|
-
|
|
305
|
+
|
|
225
|
-
|
|
306
|
+
final store = Store<AppState>(initialState: AppState(bible: bible));
|
|
226
|
-
|
|
307
|
+
await store.dispatchAndWait(SelectVerseAction(verseInChapter0));
|
|
227
|
-
|
|
308
|
+
await store.dispatchAndWait(SelectVerseAction(verseInChapter1));
|
|
228
|
-
|
|
309
|
+
expect(store.state.selectedVerses.length, 2);
|
|
229
|
-
|
|
310
|
+
|
|
230
|
-
|
|
311
|
+
await store.dispatchAndWait(SelectVerseAction(verseInChapter0));
|
|
231
|
-
|
|
312
|
+
expect(store.state.selectedVerses, [verseInChapter1]);
|
|
313
|
+
},
|
|
232
|
-
|
|
314
|
+
);
|
|
233
315
|
});
|
|
234
316
|
|
|
235
317
|
group("SetHighlightAction", () {
|
|
@@ -238,9 +320,17 @@ void main() {
|
|
|
238
320
|
final verse = bible.bookAt(0)!.chapterAt(0)!.verseAt(0)!;
|
|
239
321
|
final existingHistory = List.generate(
|
|
240
322
|
500,
|
|
323
|
+
(i) => HighlightHistoryEntry(
|
|
324
|
+
book: 0,
|
|
325
|
+
chapter: 0,
|
|
326
|
+
verseIndex: 1,
|
|
327
|
+
colorIndex: 0,
|
|
328
|
+
timestamp: DateTime.utc(2026, 1, 1),
|
|
329
|
+
),
|
|
330
|
+
);
|
|
331
|
+
final store = Store<AppState>(
|
|
241
|
-
|
|
332
|
+
initialState: AppState(bible: bible, highlightHistory: existingHistory),
|
|
242
333
|
);
|
|
243
|
-
final store = Store<AppState>(initialState: AppState(bible: bible, highlightHistory: existingHistory));
|
|
244
334
|
await store.dispatchAndWait(SetHighlightAction([verse], 2));
|
|
245
335
|
expect(store.state.highlightHistory.length, 500);
|
|
246
336
|
expect(store.state.highlights["0:0:0"], 2);
|
|
@@ -252,7 +342,11 @@ void main() {
|
|
|
252
342
|
final bible = buildTestBible();
|
|
253
343
|
final verse = bible.bookAt(0)!.chapterAt(0)!.verseAt(0)!;
|
|
254
344
|
final store = Store<AppState>(
|
|
345
|
+
initialState: AppState(
|
|
346
|
+
bible: bible,
|
|
255
|
-
|
|
347
|
+
highlights: {"0:0:0": 1},
|
|
348
|
+
selectedVerses: [verse],
|
|
349
|
+
),
|
|
256
350
|
);
|
|
257
351
|
await store.dispatchAndWait(RemoveHighlightAction([verse]));
|
|
258
352
|
expect(store.state.highlights.containsKey("0:0:0"), isFalse);
|
|
@@ -260,15 +354,6 @@ void main() {
|
|
|
260
354
|
});
|
|
261
355
|
});
|
|
262
356
|
|
|
263
|
-
// NextChapterAction/PreviousChapterAction/GoToChapterAction all call
|
|
264
|
-
// stopAudioPlayback(), which touches flutter_soloud's FFI bindings. Those
|
|
265
|
-
// bindings dlsym a native symbol that is only present once the plugin's
|
|
266
|
-
// native library is loaded by a real app process — never true in this
|
|
267
|
-
// headless `flutter test` VM — so dispatching those actions directly here
|
|
268
|
-
// always throws regardless of the app logic under test. Instead we test
|
|
269
|
-
// the pure decision logic each action delegates to (adjacentChapter) and
|
|
270
|
-
// the plain sync action (UpdateChapterAction) that the ordering fix in
|
|
271
|
-
// GoToChapterAction relies on.
|
|
272
357
|
group("adjacentChapter", () {
|
|
273
358
|
test("moves to the next chapter within the same book", () {
|
|
274
359
|
final bible = buildTestBible();
|
|
@@ -304,7 +389,9 @@ void main() {
|
|
|
304
389
|
group("UpdateChapterAction", () {
|
|
305
390
|
test("updates savedBook/savedChapter", () async {
|
|
306
391
|
final bible = buildTestBible();
|
|
392
|
+
final store = Store<AppState>(
|
|
307
|
-
|
|
393
|
+
initialState: AppState(bible: bible, savedBook: 0, savedChapter: 0),
|
|
394
|
+
);
|
|
308
395
|
await store.dispatchAndWait(UpdateChapterAction(1, 0));
|
|
309
396
|
expect(store.state.savedBook, 1);
|
|
310
397
|
expect(store.state.savedChapter, 0);
|
|
@@ -314,7 +401,9 @@ void main() {
|
|
|
314
401
|
group("SyncCurrentChapterAction", () {
|
|
315
402
|
test("is a no-op when book/chapter already match", () async {
|
|
316
403
|
final bible = buildTestBible();
|
|
404
|
+
final store = Store<AppState>(
|
|
317
|
-
|
|
405
|
+
initialState: AppState(bible: bible, savedBook: 1, savedChapter: 0),
|
|
406
|
+
);
|
|
318
407
|
final stateBefore = store.state;
|
|
319
408
|
await store.dispatchAndWait(SyncCurrentChapterAction(1, 0));
|
|
320
409
|
expect(identical(store.state, stateBefore), isTrue);
|
|
@@ -322,19 +411,110 @@ void main() {
|
|
|
322
411
|
|
|
323
412
|
test("updates state when book/chapter differ", () async {
|
|
324
413
|
final bible = buildTestBible();
|
|
414
|
+
final store = Store<AppState>(
|
|
325
|
-
|
|
415
|
+
initialState: AppState(bible: bible, savedBook: 0, savedChapter: 0),
|
|
416
|
+
);
|
|
326
417
|
await store.dispatchAndWait(SyncCurrentChapterAction(1, 0));
|
|
327
418
|
expect(store.state.savedBook, 1);
|
|
328
419
|
expect(store.state.savedChapter, 0);
|
|
329
420
|
});
|
|
330
421
|
});
|
|
331
422
|
|
|
423
|
+
// These used to be untestable here: they all call stopAudioPlayback(),
|
|
424
|
+
// which touched flutter_soloud's FFI bindings and dlsym'd a native symbol
|
|
425
|
+
// only present once the plugin's native library was loaded by a real app
|
|
426
|
+
// process, crashing in this headless VM regardless of the logic under
|
|
427
|
+
// test. Switching to audioplayers (a plain platform-channel plugin, lazily
|
|
428
|
+
// constructed) removed that: stopAudioPlayback() is a no-op whenever
|
|
429
|
+
// playback was never started, which is always true here.
|
|
430
|
+
group("GoToChapterAction", () {
|
|
431
|
+
test("updates savedBook/savedChapter and pushes the route", () async {
|
|
432
|
+
final bible = buildTestBible();
|
|
433
|
+
final store = Store<AppState>(
|
|
434
|
+
initialState: AppState(bible: bible, savedBook: 0, savedChapter: 0),
|
|
435
|
+
);
|
|
436
|
+
final router = buildTestRouter();
|
|
437
|
+
await store.dispatchAndWait(GoToChapterAction(router, 2, 1));
|
|
438
|
+
expect(store.state.savedBook, 2);
|
|
439
|
+
expect(store.state.savedChapter, 1);
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
|
|
332
|
-
group("
|
|
443
|
+
group("NextChapterAction", () {
|
|
444
|
+
test("advances to the next chapter and updates saved state", () async {
|
|
445
|
+
final bible = buildTestBible();
|
|
446
|
+
final store = Store<AppState>(
|
|
447
|
+
initialState: AppState(bible: bible, savedBook: 0, savedChapter: 0),
|
|
448
|
+
);
|
|
449
|
+
final router = buildTestRouter();
|
|
450
|
+
await store.dispatchAndWait(NextChapterAction(router, bible, 0, 0));
|
|
451
|
+
expect(store.state.savedBook, 0);
|
|
452
|
+
expect(store.state.savedChapter, 1);
|
|
453
|
+
});
|
|
454
|
+
|
|
333
|
-
test("
|
|
455
|
+
test("is a no-op after the very last chapter of the last book", () async {
|
|
456
|
+
final bible = buildTestBible();
|
|
457
|
+
final store = Store<AppState>(
|
|
334
|
-
|
|
458
|
+
initialState: AppState(bible: bible, savedBook: 2, savedChapter: 1),
|
|
459
|
+
);
|
|
460
|
+
final router = buildTestRouter();
|
|
461
|
+
await store.dispatchAndWait(NextChapterAction(router, bible, 2, 1));
|
|
462
|
+
expect(store.state.savedBook, 2);
|
|
463
|
+
expect(store.state.savedChapter, 1);
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
group("PreviousChapterAction", () {
|
|
468
|
+
test("moves to the previous chapter and updates saved state", () async {
|
|
469
|
+
final bible = buildTestBible();
|
|
470
|
+
final store = Store<AppState>(
|
|
471
|
+
initialState: AppState(bible: bible, savedBook: 1, savedChapter: 0),
|
|
472
|
+
);
|
|
473
|
+
final router = buildTestRouter();
|
|
474
|
+
await store.dispatchAndWait(PreviousChapterAction(router, bible, 1, 0));
|
|
475
|
+
expect(store.state.savedBook, 0);
|
|
476
|
+
expect(store.state.savedChapter, 1);
|
|
335
477
|
});
|
|
336
478
|
});
|
|
337
479
|
|
|
480
|
+
group("PopChapterAction", () {
|
|
481
|
+
testWidgets("restores the pre-push savedBook/savedChapter synchronously, "
|
|
482
|
+
"without waiting on the router's pop-transition listener", (
|
|
483
|
+
tester,
|
|
484
|
+
) async {
|
|
485
|
+
final bible = buildTestBible();
|
|
486
|
+
final store = Store<AppState>(
|
|
487
|
+
initialState: AppState(bible: bible, savedBook: 0, savedChapter: 0),
|
|
488
|
+
);
|
|
489
|
+
final router = buildTestRouter();
|
|
490
|
+
// A mounted Navigator is required for router.pop() to find something
|
|
491
|
+
// to pop — router.push() alone (used by the other actions above)
|
|
492
|
+
// doesn't need one.
|
|
493
|
+
await tester.pumpWidget(MaterialApp.router(routerConfig: router));
|
|
494
|
+
|
|
495
|
+
await store.dispatchAndWait(GoToChapterAction(router, 2, 1));
|
|
496
|
+
await tester.pumpAndSettle();
|
|
497
|
+
expect(store.state.savedBook, 2);
|
|
498
|
+
expect(store.state.savedChapter, 1);
|
|
499
|
+
|
|
500
|
+
await store.dispatchAndWait(PopChapterAction(router));
|
|
501
|
+
expect(store.state.savedBook, 0);
|
|
502
|
+
expect(store.state.savedChapter, 0);
|
|
503
|
+
});
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
group("theme", () {
|
|
507
|
+
test(
|
|
508
|
+
"dark theme labelMedium (verse numbers) uses the dark error accent, matching the light theme's dedicated red",
|
|
509
|
+
() {
|
|
510
|
+
expect(
|
|
511
|
+
AppTheme.dark.textTheme.labelMedium!.color,
|
|
512
|
+
darkColorScheme.error,
|
|
513
|
+
);
|
|
514
|
+
},
|
|
515
|
+
);
|
|
516
|
+
});
|
|
517
|
+
|
|
338
518
|
group("Env", () {
|
|
339
519
|
test("obfuscated secrets decode back to non-empty strings", () {
|
|
340
520
|
expect(Env.errorReportSecret, isNotEmpty);
|
|
@@ -354,24 +534,43 @@ void main() {
|
|
|
354
534
|
await tempDir.delete(recursive: true);
|
|
355
535
|
});
|
|
356
536
|
|
|
537
|
+
test(
|
|
357
|
-
|
|
538
|
+
"persistDifference writes atomically, leaving no leftover temp file",
|
|
539
|
+
() async {
|
|
358
|
-
|
|
540
|
+
final bible = buildTestBible();
|
|
359
|
-
|
|
541
|
+
final persistor = AppPersistor();
|
|
542
|
+
final state = AppState(
|
|
543
|
+
bible: bible,
|
|
544
|
+
savedBook: 1,
|
|
545
|
+
savedChapter: 0,
|
|
360
|
-
|
|
546
|
+
themeMode: ThemeMode.dark,
|
|
361
|
-
|
|
547
|
+
);
|
|
548
|
+
|
|
362
|
-
|
|
549
|
+
await persistor.persistDifference(
|
|
363
|
-
|
|
550
|
+
lastPersistedState: null,
|
|
551
|
+
newState: state,
|
|
552
|
+
);
|
|
553
|
+
|
|
364
|
-
|
|
554
|
+
final json = await persistor.readJson();
|
|
365
|
-
|
|
555
|
+
expect(json, isNotNull);
|
|
366
|
-
|
|
556
|
+
expect(json!["savedBook"], 1);
|
|
367
|
-
|
|
557
|
+
expect(json["themeMode"], "dark");
|
|
558
|
+
expect(
|
|
368
|
-
|
|
559
|
+
await File("${tempDir.path}/app_state.json.tmp").exists(),
|
|
560
|
+
isFalse,
|
|
369
|
-
|
|
561
|
+
);
|
|
370
|
-
|
|
562
|
+
},
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
test(
|
|
371
|
-
|
|
566
|
+
"readJson returns null for corrupt JSON instead of throwing",
|
|
567
|
+
() async {
|
|
372
|
-
|
|
568
|
+
final persistor = AppPersistor();
|
|
569
|
+
await File(
|
|
373
|
-
|
|
570
|
+
"${tempDir.path}/app_state.json",
|
|
571
|
+
).writeAsString("{not valid json");
|
|
374
|
-
|
|
572
|
+
expect(await persistor.readJson(), isNull);
|
|
573
|
+
},
|
|
375
|
-
|
|
574
|
+
);
|
|
376
575
|
});
|
|
377
576
|
}
|
test/screenshot_test.dart
CHANGED
|
@@ -10,7 +10,7 @@ import "package:only_bible_app/gen/bible.gen.dart";
|
|
|
10
10
|
import "package:only_bible_app/home.dart";
|
|
11
11
|
import "package:only_bible_app/store/app_navigator.dart";
|
|
12
12
|
import "package:only_bible_app/store/app_state.dart";
|
|
13
|
-
import "package:
|
|
13
|
+
import "package:only_bible_app/theme.dart";
|
|
14
14
|
import "package:only_bible_app/utils.dart";
|
|
15
15
|
import "package:path/path.dart" as path;
|
|
16
16
|
|