~repos /only-bible-app
GIT_CONFIG_PARAMETERS="'http.version=HTTP/1.1'" git clone
https://git.pyrossh.dev/only-bible-app.git
Discussions:
https://groups.google.com/g/rust-embed-devs
The only bible app you will ever need. No ads. No in-app purchases. No distractions.
c56cda8e
—
pyrossh 1 month ago
improve theme
- .gitignore +2 -1
- assets/bibles/{bn.bin → bn.bin.gz} +0 -0
- assets/bibles/{en_asv.bin → en_asv.bin.gz} +0 -0
- assets/bibles/{en_bsb.bin → en_bsb.bin.gz} +0 -0
- assets/bibles/{en_kjv.bin → en_kjv.bin.gz} +0 -0
- assets/bibles/{en_net.bin → en_net.bin.gz} +0 -0
- assets/bibles/{gu.bin → gu.bin.gz} +0 -0
- assets/bibles/{hi.bin → hi.bin.gz} +0 -0
- assets/bibles/{ja.bin → ja.bin.gz} +0 -0
- assets/bibles/{kn.bin → kn.bin.gz} +0 -0
- assets/bibles/{ml.bin → ml.bin.gz} +0 -0
- assets/bibles/{ne.bin → ne.bin.gz} +0 -0
- assets/bibles/{or.bin → or.bin.gz} +0 -0
- assets/bibles/{pa.bin → pa.bin.gz} +0 -0
- assets/bibles/{ta.bin → ta.bin.gz} +0 -0
- assets/bibles/{te.bin → te.bin.gz} +0 -0
- lib/dialog.dart +4 -2
- lib/home.dart +10 -5
- lib/store/actions_state.dart +6 -2
- lib/store/app_state.dart +11 -14
- lib/theme.dart +72 -1
- lib/utils.dart +1 -1
- lib/widgets/book_select_sheet.dart +0 -8
- lib/widgets/book_tile.dart +7 -4
- lib/widgets/home_app_bar.dart +26 -21
- lib/widgets/settings_sheet.dart +31 -18
- pubspec.lock +8 -0
- pubspec.yaml +1 -0
- screenshots/ipadScreenshots/book_select.png +0 -0
- screenshots/ipadScreenshots/chapter_select.png +0 -0
- screenshots/ipadScreenshots/home_dark.png +0 -0
- screenshots/ipadScreenshots/home_light.png +0 -0
- screenshots/ipadScreenshots/settings.png +0 -0
- screenshots/ipadScreenshots/verse_select.png +0 -0
- screenshots/iphoneScreenshots/book_select.png +0 -0
- screenshots/iphoneScreenshots/chapter_select.png +0 -0
- screenshots/iphoneScreenshots/home_dark.png +0 -0
- screenshots/iphoneScreenshots/home_light.png +0 -0
- screenshots/iphoneScreenshots/settings.png +0 -0
- screenshots/iphoneScreenshots/verse_select.png +0 -0
- screenshots/phoneScreenshots/book_select.png +0 -0
- screenshots/phoneScreenshots/chapter_select.png +0 -0
- screenshots/phoneScreenshots/home_dark.png +0 -0
- screenshots/phoneScreenshots/home_light.png +0 -0
- screenshots/phoneScreenshots/settings.png +0 -0
- screenshots/phoneScreenshots/verse_select.png +0 -0
- scripts/generate_flatbuffers.dart +1 -2
- scripts/scrape.dart +0 -167
- test/screenshot_test.dart +24 -12
- test/widget_test.dart +0 -30
- tts.dart +0 -127
.gitignore
CHANGED
|
@@ -11,4 +11,5 @@ ui
|
|
|
11
11
|
.flutter-plugins-dependencies
|
|
12
12
|
.env
|
|
13
13
|
.idea
|
|
14
|
-
test/failures
|
|
14
|
+
test/failures
|
|
15
|
+
engbsb_usfm
|
assets/bibles/{bn.bin → bn.bin.gz}
RENAMED
|
File without changes
|
assets/bibles/{en_asv.bin → en_asv.bin.gz}
RENAMED
|
File without changes
|
assets/bibles/{en_bsb.bin → en_bsb.bin.gz}
RENAMED
|
File without changes
|
assets/bibles/{en_kjv.bin → en_kjv.bin.gz}
RENAMED
|
File without changes
|
assets/bibles/{en_net.bin → en_net.bin.gz}
RENAMED
|
File without changes
|
assets/bibles/{gu.bin → gu.bin.gz}
RENAMED
|
File without changes
|
assets/bibles/{hi.bin → hi.bin.gz}
RENAMED
|
File without changes
|
assets/bibles/{ja.bin → ja.bin.gz}
RENAMED
|
File without changes
|
assets/bibles/{kn.bin → kn.bin.gz}
RENAMED
|
File without changes
|
assets/bibles/{ml.bin → ml.bin.gz}
RENAMED
|
File without changes
|
assets/bibles/{ne.bin → ne.bin.gz}
RENAMED
|
File without changes
|
assets/bibles/{or.bin → or.bin.gz}
RENAMED
|
File without changes
|
assets/bibles/{pa.bin → pa.bin.gz}
RENAMED
|
File without changes
|
assets/bibles/{ta.bin → ta.bin.gz}
RENAMED
|
File without changes
|
assets/bibles/{te.bin → te.bin.gz}
RENAMED
|
File without changes
|
lib/dialog.dart
CHANGED
|
@@ -55,8 +55,10 @@ void showBibleSelectDialog(BuildContext context, Bible bible) {
|
|
|
55
55
|
return ListTile(
|
|
56
56
|
selected: isSelected,
|
|
57
57
|
leading: const Icon(Icons.menu_book),
|
|
58
|
-
title: Text(item.languageNative),
|
|
58
|
+
title: Text(item.languageNative, style: const TextStyle(fontWeight: FontWeight.w600)),
|
|
59
|
-
subtitle: (item.languageNative != item.languageEnglish)
|
|
59
|
+
subtitle: (item.languageNative != item.languageEnglish)
|
|
60
|
+
? Text(item.languageEnglish, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w400))
|
|
61
|
+
: null,
|
|
60
62
|
trailing: isSelected ? const Icon(Icons.check) : null,
|
|
61
63
|
onTap: () {
|
|
62
64
|
final s = context.read();
|
lib/home.dart
CHANGED
|
@@ -58,11 +58,11 @@ class Home extends StatelessWidget {
|
|
|
58
58
|
final bible = context.select((s) => s.bible);
|
|
59
59
|
final book = bible.books![bookIndex];
|
|
60
60
|
final chapter = book.chapters![chapterIndex];
|
|
61
|
-
final (
|
|
61
|
+
final (fontWeight, fontSize, selectedVerses, _, _) =
|
|
62
|
-
context.select((s) => (s.
|
|
62
|
+
context.select((s) => (s.fontWeight, s.fontSize, s.selectedVerses, s.highlights, s.darkMode));
|
|
63
63
|
final appState = context.read();
|
|
64
64
|
final baseStyle = theme.bodyMedium!.copyWith(
|
|
65
|
-
fontWeight:
|
|
65
|
+
fontWeight: FontWeight.values[(fontWeight ~/ 100) - 1],
|
|
66
66
|
fontSize: fontSize,
|
|
67
67
|
);
|
|
68
68
|
return Scaffold(
|
|
@@ -94,7 +94,12 @@ class Home extends StatelessWidget {
|
|
|
94
94
|
style: baseStyle,
|
|
95
95
|
children: [
|
|
96
96
|
if (v.heading != null && v.heading!.isNotEmpty)
|
|
97
|
-
..._buildHeadingSpans(
|
|
97
|
+
..._buildHeadingSpans(
|
|
98
|
+
context,
|
|
99
|
+
bible,
|
|
100
|
+
v.heading!,
|
|
101
|
+
theme.labelLarge!,
|
|
102
|
+
),
|
|
98
103
|
TextSpan(
|
|
99
104
|
text: "${v.index + 1} ",
|
|
100
105
|
style: theme.labelMedium!.copyWith(
|
|
@@ -103,7 +108,7 @@ class Home extends StatelessWidget {
|
|
|
103
108
|
),
|
|
104
109
|
TextSpan(
|
|
105
110
|
text: v.text ?? "",
|
|
106
|
-
style: appState.getHighlightStyle(context, v
|
|
111
|
+
style: appState.getHighlightStyle(context, v),
|
|
107
112
|
),
|
|
108
113
|
],
|
|
109
114
|
),
|
lib/store/actions_state.dart
CHANGED
|
@@ -13,9 +13,13 @@ class ToggleEngTitlesAction extends ReduxAction<AppState> {
|
|
|
13
13
|
AppState reduce() => state.copy(engTitles: !state.engTitles);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
class
|
|
16
|
+
class UpdateFontWeightAction extends ReduxAction<AppState> {
|
|
17
|
+
final int value;
|
|
18
|
+
|
|
19
|
+
UpdateFontWeightAction(this.value);
|
|
20
|
+
|
|
17
21
|
@override
|
|
18
|
-
AppState reduce() => state.copy(
|
|
22
|
+
AppState reduce() => state.copy(fontWeight: value);
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
class ToggleDarkModeAction extends ReduxAction<AppState> {
|
lib/store/app_state.dart
CHANGED
|
@@ -6,7 +6,7 @@ export "package:async_redux/async_redux.dart" show BuildContextExtensionForProvi
|
|
|
6
6
|
|
|
7
7
|
class AppState {
|
|
8
8
|
final bool engTitles;
|
|
9
|
-
final
|
|
9
|
+
final int fontWeight;
|
|
10
10
|
final bool darkMode;
|
|
11
11
|
final double fontSize;
|
|
12
12
|
final int savedBook;
|
|
@@ -17,7 +17,7 @@ class AppState {
|
|
|
17
17
|
|
|
18
18
|
AppState({
|
|
19
19
|
this.engTitles = false,
|
|
20
|
-
this.
|
|
20
|
+
this.fontWeight = 400,
|
|
21
21
|
this.darkMode = false,
|
|
22
22
|
this.fontSize = 16.0,
|
|
23
23
|
this.savedBook = 0,
|
|
@@ -29,7 +29,7 @@ class AppState {
|
|
|
29
29
|
|
|
30
30
|
AppState copy({
|
|
31
31
|
bool? engTitles,
|
|
32
|
-
|
|
32
|
+
int? fontWeight,
|
|
33
33
|
bool? darkMode,
|
|
34
34
|
double? fontSize,
|
|
35
35
|
int? savedBook,
|
|
@@ -40,7 +40,7 @@ class AppState {
|
|
|
40
40
|
}) {
|
|
41
41
|
return AppState(
|
|
42
42
|
engTitles: engTitles ?? this.engTitles,
|
|
43
|
-
|
|
43
|
+
fontWeight: fontWeight ?? this.fontWeight,
|
|
44
44
|
darkMode: darkMode ?? this.darkMode,
|
|
45
45
|
fontSize: fontSize ?? this.fontSize,
|
|
46
46
|
savedBook: savedBook ?? this.savedBook,
|
|
@@ -54,7 +54,7 @@ class AppState {
|
|
|
54
54
|
Map<String, dynamic> toJson() => {
|
|
55
55
|
"bibleName": bible.name,
|
|
56
56
|
"engTitles": engTitles,
|
|
57
|
-
"
|
|
57
|
+
"fontWeight": fontWeight,
|
|
58
58
|
"darkMode": darkMode,
|
|
59
59
|
"fontSize": fontSize,
|
|
60
60
|
"savedBook": savedBook,
|
|
@@ -64,7 +64,7 @@ class AppState {
|
|
|
64
64
|
|
|
65
65
|
factory AppState.fromJson(Map<String, dynamic> json, Bible bible) => AppState(
|
|
66
66
|
engTitles: json["engTitles"] as bool? ?? false,
|
|
67
|
-
|
|
67
|
+
fontWeight: json["fontWeight"] as int? ?? (json["boldFont"] == true ? 500 : 400),
|
|
68
68
|
darkMode: json["darkMode"] as bool? ?? false,
|
|
69
69
|
fontSize: (json["fontSize"] as num?)?.toDouble() ?? ((json["textScale"] as num?)?.toDouble() ?? 0.0) + 16.0,
|
|
70
70
|
savedBook: json["savedBook"] as int? ?? 0,
|
|
@@ -86,25 +86,22 @@ class AppState {
|
|
|
86
86
|
);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
TextStyle getHighlightStyle(BuildContext context, Verse v
|
|
89
|
+
TextStyle getHighlightStyle(BuildContext context, Verse v) {
|
|
90
90
|
final isSelected = selectedVerses.any(
|
|
91
91
|
(el) => el.book == v.book && el.chapter == v.chapter && el.index == v.index,
|
|
92
92
|
);
|
|
93
|
-
|
|
94
|
-
if (
|
|
93
|
+
if (isSelected) {
|
|
95
94
|
return TextStyle(
|
|
96
|
-
backgroundColor:
|
|
95
|
+
backgroundColor: Theme.of(context).textSelectionTheme.selectionColor,
|
|
97
96
|
);
|
|
98
97
|
}
|
|
99
|
-
final highlight = getHighlight(v);
|
|
100
98
|
if (darkMode) {
|
|
101
99
|
return TextStyle(
|
|
102
|
-
backgroundColor: highlight?.withValues(alpha: 0.7),
|
|
103
|
-
color:
|
|
100
|
+
color: getHighlight(v) ?? Theme.of(context).colorScheme.onSurface,
|
|
104
101
|
);
|
|
105
102
|
}
|
|
106
103
|
return TextStyle(
|
|
107
|
-
backgroundColor:
|
|
104
|
+
backgroundColor: getHighlight(v) ?? Theme.of(context).colorScheme.surface,
|
|
108
105
|
);
|
|
109
106
|
}
|
|
110
107
|
}
|
lib/theme.dart
CHANGED
|
@@ -104,7 +104,7 @@ final lightTheme = ThemeData(
|
|
|
104
104
|
textSelectionTheme: TextSelectionThemeData(
|
|
105
105
|
cursorColor: Colors.black,
|
|
106
106
|
selectionHandleColor: Colors.black,
|
|
107
|
-
selectionColor:
|
|
107
|
+
selectionColor: Colors.grey.shade200,
|
|
108
108
|
),
|
|
109
109
|
inputDecorationTheme: const InputDecorationTheme(
|
|
110
110
|
focusColor: Colors.transparent,
|
|
@@ -165,6 +165,39 @@ final lightTheme = ThemeData(
|
|
|
165
165
|
),
|
|
166
166
|
),
|
|
167
167
|
),
|
|
168
|
+
segmentedButtonTheme: SegmentedButtonThemeData(
|
|
169
|
+
style: ButtonStyle(
|
|
170
|
+
backgroundColor: WidgetStateProperty.resolveWith((states) {
|
|
171
|
+
if (states.contains(WidgetState.selected)) {
|
|
172
|
+
return lightColorScheme.primary;
|
|
173
|
+
}
|
|
174
|
+
return Colors.grey.shade100;
|
|
175
|
+
}),
|
|
176
|
+
foregroundColor: WidgetStateProperty.resolveWith((states) {
|
|
177
|
+
if (states.contains(WidgetState.selected)) {
|
|
178
|
+
return lightColorScheme.onPrimary;
|
|
179
|
+
}
|
|
180
|
+
return lightColorScheme.onSurface;
|
|
181
|
+
}),
|
|
182
|
+
textStyle: WidgetStatePropertyAll(
|
|
183
|
+
TextStyle(
|
|
184
|
+
fontSize: 16,
|
|
185
|
+
fontWeight: FontWeight.w700,
|
|
186
|
+
color: lightColorScheme.onSurface,
|
|
187
|
+
letterSpacing: 0,
|
|
188
|
+
),
|
|
189
|
+
),
|
|
190
|
+
),
|
|
191
|
+
),
|
|
192
|
+
toggleButtonsTheme: ToggleButtonsThemeData(
|
|
193
|
+
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
|
194
|
+
borderColor: lightColorScheme.scrim,
|
|
195
|
+
selectedBorderColor: lightColorScheme.scrim,
|
|
196
|
+
selectedColor: lightColorScheme.onPrimary,
|
|
197
|
+
fillColor: lightColorScheme.primary,
|
|
198
|
+
color: lightColorScheme.onSurface,
|
|
199
|
+
constraints: const BoxConstraints(minHeight: 40, minWidth: 140),
|
|
200
|
+
),
|
|
168
201
|
textTheme: TextTheme(
|
|
169
202
|
bodyMedium: TextStyle(
|
|
170
203
|
fontSize: 16,
|
|
@@ -209,6 +242,39 @@ final lightTheme = ThemeData(
|
|
|
209
242
|
final darkTheme = lightTheme.copyWith(
|
|
210
243
|
brightness: Brightness.dark,
|
|
211
244
|
colorScheme: darkColorScheme,
|
|
245
|
+
segmentedButtonTheme: SegmentedButtonThemeData(
|
|
246
|
+
style: ButtonStyle(
|
|
247
|
+
backgroundColor: WidgetStateProperty.resolveWith((states) {
|
|
248
|
+
if (states.contains(WidgetState.selected)) {
|
|
249
|
+
return darkColorScheme.primary;
|
|
250
|
+
}
|
|
251
|
+
return darkColorScheme.surfaceContainerHigh;
|
|
252
|
+
}),
|
|
253
|
+
foregroundColor: WidgetStateProperty.resolveWith((states) {
|
|
254
|
+
if (states.contains(WidgetState.selected)) {
|
|
255
|
+
return darkColorScheme.onPrimary;
|
|
256
|
+
}
|
|
257
|
+
return darkColorScheme.onSurface;
|
|
258
|
+
}),
|
|
259
|
+
textStyle: WidgetStatePropertyAll(
|
|
260
|
+
TextStyle(
|
|
261
|
+
fontSize: 16,
|
|
262
|
+
fontWeight: FontWeight.w700,
|
|
263
|
+
color: darkColorScheme.onSurface,
|
|
264
|
+
letterSpacing: 0,
|
|
265
|
+
),
|
|
266
|
+
),
|
|
267
|
+
),
|
|
268
|
+
),
|
|
269
|
+
toggleButtonsTheme: ToggleButtonsThemeData(
|
|
270
|
+
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
|
271
|
+
borderColor: darkColorScheme.outline,
|
|
272
|
+
selectedBorderColor: darkColorScheme.outline,
|
|
273
|
+
selectedColor: darkColorScheme.onPrimary,
|
|
274
|
+
fillColor: darkColorScheme.primary,
|
|
275
|
+
color: darkColorScheme.onSurface,
|
|
276
|
+
constraints: const BoxConstraints(minHeight: 40, minWidth: 140),
|
|
277
|
+
),
|
|
212
278
|
primaryColor: const Color(0xFF4C2323),
|
|
213
279
|
primaryColorDark: const Color(0xFF3C1B1C),
|
|
214
280
|
primaryColorLight: const Color(0xFF7F3D3C),
|
|
@@ -227,6 +293,11 @@ final darkTheme = lightTheme.copyWith(
|
|
|
227
293
|
shadowColor: Colors.white,
|
|
228
294
|
surfaceTintColor: const Color(0xFF141415),
|
|
229
295
|
),
|
|
296
|
+
textSelectionTheme: TextSelectionThemeData(
|
|
297
|
+
cursorColor: Colors.white,
|
|
298
|
+
selectionHandleColor: Colors.white,
|
|
299
|
+
selectionColor: Colors.blueGrey.shade800,
|
|
300
|
+
),
|
|
230
301
|
dialogTheme: DialogThemeData(
|
|
231
302
|
backgroundColor: darkColorScheme.surface,
|
|
232
303
|
elevation: 1,
|
lib/utils.dart
CHANGED
|
@@ -75,7 +75,7 @@ Future<Uint8List> convertText(String langCode, String text) async {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
Future<Bible> loadBible(String name) async {
|
|
78
|
-
final data = await rootBundle.load("assets/bibles/$name.bin");
|
|
78
|
+
final data = await rootBundle.load("assets/bibles/$name.bin.gz");
|
|
79
79
|
final decompressed = gzip.decode(data.buffer.asUint8List());
|
|
80
80
|
return Bible(decompressed);
|
|
81
81
|
}
|
lib/widgets/book_select_sheet.dart
CHANGED
|
@@ -67,7 +67,6 @@ class _BookSelectSheetState extends State<BookSelectSheet> {
|
|
|
67
67
|
Widget build(BuildContext context) {
|
|
68
68
|
final currentBook = widget.parentContext.read().savedBook;
|
|
69
69
|
final currentChapter = widget.parentContext.read().savedChapter;
|
|
70
|
-
final colorScheme = Theme.of(context).colorScheme;
|
|
71
70
|
|
|
72
71
|
return PageView(
|
|
73
72
|
controller: _pageController,
|
|
@@ -84,14 +83,7 @@ class _BookSelectSheetState extends State<BookSelectSheet> {
|
|
|
84
83
|
showOldTestament = index == 0;
|
|
85
84
|
});
|
|
86
85
|
},
|
|
87
|
-
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
|
88
|
-
borderColor: colorScheme.scrim,
|
|
89
|
-
selectedBorderColor: colorScheme.scrim,
|
|
90
|
-
selectedColor: colorScheme.onPrimary,
|
|
91
|
-
fillColor: colorScheme.primary,
|
|
92
|
-
color: colorScheme.onSurface,
|
|
93
86
|
textStyle: Theme.of(context).textTheme.labelLarge,
|
|
94
|
-
constraints: const BoxConstraints(minHeight: 40, minWidth: 140),
|
|
95
87
|
isSelected: [showOldTestament, !showOldTestament],
|
|
96
88
|
children: [
|
|
97
89
|
Text(widget.bible.oldTestamentTitle!),
|
lib/widgets/book_tile.dart
CHANGED
|
@@ -10,14 +10,17 @@ class BookTile extends StatelessWidget {
|
|
|
10
10
|
@override
|
|
11
11
|
Widget build(BuildContext context) {
|
|
12
12
|
final colorScheme = Theme.of(context).colorScheme;
|
|
13
|
+
final isDark = Theme.of(context).brightness == Brightness.dark;
|
|
14
|
+
final tileColor = isDark ? colorScheme.surfaceContainerHigh : colorScheme.surface;
|
|
15
|
+
final borderColor = isDark ? colorScheme.outlineVariant : colorScheme.scrim;
|
|
13
16
|
return Material(
|
|
14
|
-
elevation: 4,
|
|
17
|
+
elevation: isDark ? 2 : 4,
|
|
15
|
-
color: isSelected ? colorScheme.primaryContainer :
|
|
18
|
+
color: isSelected ? colorScheme.primaryContainer : tileColor,
|
|
16
|
-
shadowColor: colorScheme.
|
|
19
|
+
shadowColor: colorScheme.shadow,
|
|
17
20
|
shape: RoundedRectangleBorder(
|
|
18
21
|
borderRadius: BorderRadius.circular(12),
|
|
19
22
|
side: BorderSide(
|
|
20
|
-
color: isSelected ? colorScheme.primary :
|
|
23
|
+
color: isSelected ? colorScheme.primary : borderColor,
|
|
21
24
|
width: isSelected ? 2 : 1,
|
|
22
25
|
),
|
|
23
26
|
),
|
lib/widgets/home_app_bar.dart
CHANGED
|
@@ -32,17 +32,14 @@ class HomeAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|
|
32
32
|
child: Row(
|
|
33
33
|
mainAxisSize: MainAxisSize.min,
|
|
34
34
|
children: [
|
|
35
|
-
Flexible(
|
|
36
|
-
|
|
35
|
+
InkWell(
|
|
37
|
-
|
|
36
|
+
key: const Key("bookTitle"),
|
|
38
|
-
|
|
37
|
+
enableFeedback: true,
|
|
39
|
-
|
|
38
|
+
onTap: () => context.dispatch(ShowBookSelectAction(context, context.router, bible)),
|
|
40
|
-
|
|
39
|
+
child: Text(
|
|
41
|
-
|
|
40
|
+
book.name!,
|
|
42
|
-
|
|
41
|
+
style: Theme.of(context).textTheme.headlineMedium,
|
|
43
|
-
overflow: TextOverflow.ellipsis,
|
|
44
|
-
|
|
42
|
+
maxLines: 1,
|
|
45
|
-
),
|
|
46
43
|
),
|
|
47
44
|
),
|
|
48
45
|
InkWell(
|
|
@@ -60,19 +57,27 @@ class HomeAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|
|
60
57
|
],
|
|
61
58
|
),
|
|
62
59
|
),
|
|
60
|
+
// InkWell(
|
|
61
|
+
// enableFeedback: true,
|
|
62
|
+
// onTap: () => showBibleSelectDialog(context, bible),
|
|
63
|
+
// child: Padding(
|
|
64
|
+
// padding: const EdgeInsets.only(left: 16, right: 16),
|
|
65
|
+
// child: Text(
|
|
66
|
+
// bible.languageNative!.toUpperCase(),
|
|
67
|
+
// style: Theme.of(context).textTheme.headlineMedium,
|
|
68
|
+
// key: const Key("bible"),
|
|
69
|
+
// ),
|
|
70
|
+
// ),
|
|
71
|
+
// ),
|
|
63
|
-
|
|
72
|
+
IconButton(
|
|
64
73
|
enableFeedback: true,
|
|
65
|
-
onTap: () => showBibleSelectDialog(context, bible),
|
|
66
|
-
child: Padding(
|
|
67
|
-
padding: const EdgeInsets.only(left: 16, right: 16),
|
|
68
|
-
child: Text(
|
|
69
|
-
bible.languageNative!.toUpperCase(),
|
|
70
|
-
style: Theme.of(context).textTheme.headlineMedium,
|
|
71
|
-
|
|
74
|
+
key: const Key("bible"),
|
|
72
|
-
|
|
75
|
+
padding: EdgeInsets.zero,
|
|
73
|
-
),
|
|
76
|
+
icon: const Icon(Icons.menu_book, size: 28),
|
|
77
|
+
onPressed: () => showBibleSelectDialog(context, bible),
|
|
74
78
|
),
|
|
75
79
|
IconButton(
|
|
80
|
+
enableFeedback: true,
|
|
76
81
|
key: const Key("settingsButton"),
|
|
77
82
|
padding: EdgeInsets.zero,
|
|
78
83
|
icon: const Icon(Icons.more_vert, size: 28),
|
lib/widgets/settings_sheet.dart
CHANGED
|
@@ -11,10 +11,13 @@ class SettingsSheet extends StatelessWidget {
|
|
|
11
11
|
@override
|
|
12
12
|
Widget build(BuildContext context) {
|
|
13
13
|
final darkMode = context.select((s) => s.darkMode);
|
|
14
|
-
final
|
|
14
|
+
final fontWeight = context.select((s) => s.fontWeight);
|
|
15
15
|
final engTitles = context.select((s) => s.engTitles);
|
|
16
16
|
final fontSize = context.select((s) => s.fontSize);
|
|
17
17
|
final colorScheme = Theme.of(context).colorScheme;
|
|
18
|
+
final isDark = Theme.of(context).brightness == Brightness.dark;
|
|
19
|
+
final cardColor = isDark ? colorScheme.surfaceContainerHigh : colorScheme.surface;
|
|
20
|
+
final cardBorderColor = isDark ? colorScheme.outlineVariant : colorScheme.scrim;
|
|
18
21
|
|
|
19
22
|
return Padding(
|
|
20
23
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
@@ -29,14 +32,15 @@ class SettingsSheet extends StatelessWidget {
|
|
|
29
32
|
const SizedBox(height: 16),
|
|
30
33
|
// Theme toggle
|
|
31
34
|
Material(
|
|
32
|
-
elevation: 1,
|
|
35
|
+
elevation: isDark ? 2 : 1,
|
|
33
36
|
borderRadius: BorderRadius.circular(12),
|
|
37
|
+
color: cardColor,
|
|
34
|
-
|
|
38
|
+
shadowColor: colorScheme.shadow,
|
|
35
39
|
child: Container(
|
|
36
40
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2),
|
|
37
41
|
decoration: BoxDecoration(
|
|
38
42
|
borderRadius: BorderRadius.circular(12),
|
|
39
|
-
border: Border.all(color:
|
|
43
|
+
border: Border.all(color: cardBorderColor),
|
|
40
44
|
),
|
|
41
45
|
child: Row(
|
|
42
46
|
children: [
|
|
@@ -66,14 +70,15 @@ class SettingsSheet extends StatelessWidget {
|
|
|
66
70
|
const SizedBox(height: 8),
|
|
67
71
|
// Font size slider
|
|
68
72
|
Material(
|
|
69
|
-
elevation: 1,
|
|
73
|
+
elevation: isDark ? 2 : 1,
|
|
70
74
|
borderRadius: BorderRadius.circular(12),
|
|
75
|
+
color: cardColor,
|
|
71
|
-
|
|
76
|
+
shadowColor: colorScheme.shadow,
|
|
72
77
|
child: Container(
|
|
73
78
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2),
|
|
74
79
|
decoration: BoxDecoration(
|
|
75
80
|
borderRadius: BorderRadius.circular(12),
|
|
76
|
-
border: Border.all(color:
|
|
81
|
+
border: Border.all(color: cardBorderColor),
|
|
77
82
|
),
|
|
78
83
|
child: Row(
|
|
79
84
|
children: [
|
|
@@ -95,25 +100,32 @@ class SettingsSheet extends StatelessWidget {
|
|
|
95
100
|
),
|
|
96
101
|
),
|
|
97
102
|
const SizedBox(height: 8),
|
|
98
|
-
//
|
|
103
|
+
// Font weight slider
|
|
99
104
|
Material(
|
|
100
|
-
elevation: 1,
|
|
105
|
+
elevation: isDark ? 2 : 1,
|
|
101
106
|
borderRadius: BorderRadius.circular(12),
|
|
107
|
+
color: cardColor,
|
|
102
|
-
|
|
108
|
+
shadowColor: colorScheme.shadow,
|
|
103
109
|
child: Container(
|
|
104
110
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2),
|
|
105
111
|
decoration: BoxDecoration(
|
|
106
112
|
borderRadius: BorderRadius.circular(12),
|
|
107
|
-
border: Border.all(color:
|
|
113
|
+
border: Border.all(color: cardBorderColor),
|
|
108
114
|
),
|
|
109
115
|
child: Row(
|
|
110
116
|
children: [
|
|
111
117
|
Icon(Icons.format_bold, color: colorScheme.onSurface),
|
|
112
118
|
const SizedBox(width: 12),
|
|
113
|
-
|
|
119
|
+
Text("$fontWeight"),
|
|
114
|
-
|
|
120
|
+
Expanded(
|
|
121
|
+
child: Slider(
|
|
115
|
-
|
|
122
|
+
value: fontWeight.toDouble(),
|
|
123
|
+
min: 100,
|
|
124
|
+
max: 900,
|
|
125
|
+
divisions: 8,
|
|
126
|
+
label: "$fontWeight",
|
|
116
|
-
|
|
127
|
+
onChanged: (v) => context.dispatch(UpdateFontWeightAction(v.round())),
|
|
128
|
+
),
|
|
117
129
|
),
|
|
118
130
|
],
|
|
119
131
|
),
|
|
@@ -122,14 +134,15 @@ class SettingsSheet extends StatelessWidget {
|
|
|
122
134
|
const SizedBox(height: 8),
|
|
123
135
|
// English titles toggle
|
|
124
136
|
Material(
|
|
125
|
-
elevation: 1,
|
|
137
|
+
elevation: isDark ? 2 : 1,
|
|
126
138
|
borderRadius: BorderRadius.circular(12),
|
|
139
|
+
color: cardColor,
|
|
127
|
-
|
|
140
|
+
shadowColor: colorScheme.shadow,
|
|
128
141
|
child: Container(
|
|
129
142
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2),
|
|
130
143
|
decoration: BoxDecoration(
|
|
131
144
|
borderRadius: BorderRadius.circular(12),
|
|
132
|
-
border: Border.all(color:
|
|
145
|
+
border: Border.all(color: cardBorderColor),
|
|
133
146
|
),
|
|
134
147
|
child: Row(
|
|
135
148
|
children: [
|
pubspec.lock
CHANGED
|
@@ -249,6 +249,14 @@ packages:
|
|
|
249
249
|
url: "https://pub.dev"
|
|
250
250
|
source: hosted
|
|
251
251
|
version: "1.0.2"
|
|
252
|
+
cupertino_icons:
|
|
253
|
+
dependency: "direct main"
|
|
254
|
+
description:
|
|
255
|
+
name: cupertino_icons
|
|
256
|
+
sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
|
|
257
|
+
url: "https://pub.dev"
|
|
258
|
+
source: hosted
|
|
259
|
+
version: "1.0.9"
|
|
252
260
|
dart_style:
|
|
253
261
|
dependency: transitive
|
|
254
262
|
description:
|
pubspec.yaml
CHANGED
|
@@ -9,6 +9,7 @@ environment:
|
|
|
9
9
|
dependencies:
|
|
10
10
|
flutter:
|
|
11
11
|
sdk: flutter
|
|
12
|
+
cupertino_icons: ^1.0.8
|
|
12
13
|
path_provider: ^2.1.5
|
|
13
14
|
flutter_native_splash: ^2.4.7
|
|
14
15
|
flutter_swipe_detector: ^2.0.0
|
screenshots/ipadScreenshots/book_select.png
CHANGED
|
Binary file
|
screenshots/ipadScreenshots/chapter_select.png
CHANGED
|
Binary file
|
screenshots/ipadScreenshots/home_dark.png
CHANGED
|
Binary file
|
screenshots/ipadScreenshots/home_light.png
CHANGED
|
Binary file
|
screenshots/ipadScreenshots/settings.png
CHANGED
|
Binary file
|
screenshots/ipadScreenshots/verse_select.png
CHANGED
|
Binary file
|
screenshots/iphoneScreenshots/book_select.png
CHANGED
|
Binary file
|
screenshots/iphoneScreenshots/chapter_select.png
CHANGED
|
Binary file
|
screenshots/iphoneScreenshots/home_dark.png
CHANGED
|
Binary file
|
screenshots/iphoneScreenshots/home_light.png
CHANGED
|
Binary file
|
screenshots/iphoneScreenshots/settings.png
CHANGED
|
Binary file
|
screenshots/iphoneScreenshots/verse_select.png
CHANGED
|
Binary file
|
screenshots/phoneScreenshots/book_select.png
CHANGED
|
Binary file
|
screenshots/phoneScreenshots/chapter_select.png
CHANGED
|
Binary file
|
screenshots/phoneScreenshots/home_dark.png
CHANGED
|
Binary file
|
screenshots/phoneScreenshots/home_light.png
CHANGED
|
Binary file
|
screenshots/phoneScreenshots/settings.png
CHANGED
|
Binary file
|
screenshots/phoneScreenshots/verse_select.png
CHANGED
|
Binary file
|
scripts/generate_flatbuffers.dart
CHANGED
|
@@ -4,7 +4,6 @@ import "dart:typed_data";
|
|
|
4
4
|
import "package:flat_buffers/flat_buffers.dart" as fb;
|
|
5
5
|
import "package:only_bible_app/bible_data.dart";
|
|
6
6
|
|
|
7
|
-
|
|
8
7
|
// Converts bible .txt to FlatBuffer binary
|
|
9
8
|
// Format: bookName|bookIndex|chapterIndex|verseNo|heading|text...
|
|
10
9
|
Uint8List convertBibleToFlatBuffer(BibleItem item, String text) {
|
|
@@ -128,7 +127,7 @@ void main() async {
|
|
|
128
127
|
final text = await file.readAsString(encoding: utf8);
|
|
129
128
|
final bytes = convertBibleToFlatBuffer(item, text);
|
|
130
129
|
final compressed = gzip.encode(bytes);
|
|
131
|
-
final outPath = "assets/bibles/$fileName.bin";
|
|
130
|
+
final outPath = "assets/bibles/$fileName.bin.gz";
|
|
132
131
|
await File(outPath).writeAsBytes(compressed);
|
|
133
132
|
final txtSize = (file.lengthSync() / 1024).toStringAsFixed(1);
|
|
134
133
|
final binSize = (compressed.length / 1024).toStringAsFixed(1);
|
scripts/scrape.dart
DELETED
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import "dart:convert";
|
|
2
|
-
import "dart:io";
|
|
3
|
-
import "package:http/http.dart" as http;
|
|
4
|
-
import "package:html/parser.dart";
|
|
5
|
-
import "package:only_bible_app/models.dart";
|
|
6
|
-
|
|
7
|
-
final booksNames = [
|
|
8
|
-
"Genesis",
|
|
9
|
-
"Exodus",
|
|
10
|
-
"Leviticus",
|
|
11
|
-
"Numbers",
|
|
12
|
-
"Deuteronomy",
|
|
13
|
-
"Joshua",
|
|
14
|
-
"Judges",
|
|
15
|
-
"Ruth",
|
|
16
|
-
"1 Samuel",
|
|
17
|
-
"2 Samuel",
|
|
18
|
-
"1 Kings",
|
|
19
|
-
"2 Kings",
|
|
20
|
-
"1 Chronicles",
|
|
21
|
-
"2 Chronicles",
|
|
22
|
-
"Ezra",
|
|
23
|
-
"Nehemiah",
|
|
24
|
-
"Esther",
|
|
25
|
-
"Job",
|
|
26
|
-
"Psalms",
|
|
27
|
-
"Proverbs",
|
|
28
|
-
"Ecclesiastes",
|
|
29
|
-
"Song of Solomon",
|
|
30
|
-
"Isaiah",
|
|
31
|
-
"Jeremiah",
|
|
32
|
-
"Lamentations",
|
|
33
|
-
"Ezekiel",
|
|
34
|
-
"Daniel",
|
|
35
|
-
"Hosea",
|
|
36
|
-
"Joel",
|
|
37
|
-
"Amos",
|
|
38
|
-
"Obadiah",
|
|
39
|
-
"Jonah",
|
|
40
|
-
"Micah",
|
|
41
|
-
"Nahum",
|
|
42
|
-
"Habakkuk",
|
|
43
|
-
"Zephaniah",
|
|
44
|
-
"Haggai",
|
|
45
|
-
"Zechariah",
|
|
46
|
-
"Malachi",
|
|
47
|
-
"Matthew",
|
|
48
|
-
"Mark",
|
|
49
|
-
"Luke",
|
|
50
|
-
"John",
|
|
51
|
-
"Acts",
|
|
52
|
-
"Romans",
|
|
53
|
-
"1 Corinthians",
|
|
54
|
-
"2 Corinthians",
|
|
55
|
-
"Galatians",
|
|
56
|
-
"Ephesians",
|
|
57
|
-
"Philippians",
|
|
58
|
-
"Colossians",
|
|
59
|
-
"1 Thessalonians",
|
|
60
|
-
"2 Thessalonians",
|
|
61
|
-
"1 Timothy",
|
|
62
|
-
"2 Timothy",
|
|
63
|
-
"Titus",
|
|
64
|
-
"Philemon",
|
|
65
|
-
"Hebrews",
|
|
66
|
-
"James",
|
|
67
|
-
"1 Peter",
|
|
68
|
-
"2 Peter",
|
|
69
|
-
"1 John",
|
|
70
|
-
"2 John",
|
|
71
|
-
"3 John",
|
|
72
|
-
"Jude",
|
|
73
|
-
"Revelation",
|
|
74
|
-
];
|
|
75
|
-
|
|
76
|
-
class Heading {
|
|
77
|
-
int vIndex;
|
|
78
|
-
String text;
|
|
79
|
-
|
|
80
|
-
Heading(this.vIndex, this.text);
|
|
81
|
-
|
|
82
|
-
@override
|
|
83
|
-
toString() {
|
|
84
|
-
return "vIndex: $vIndex, text: $text";
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
Future<List<Heading>> fetchPage(String book, int chapterIndex) async {
|
|
89
|
-
print("fetching $book $chapterIndex \n");
|
|
90
|
-
final res = await http.get(
|
|
91
|
-
Uri.parse(
|
|
92
|
-
"https://raw.githubusercontent.com/kenyonbowers/Bible-JSON/main/JSON/$/1.json",
|
|
93
|
-
),
|
|
94
|
-
);
|
|
95
|
-
var document = parse(utf8.decode(res.bodyBytes, allowMalformed: true));
|
|
96
|
-
var title = document
|
|
97
|
-
.getElementsByClassName("psalm-title")
|
|
98
|
-
.map(
|
|
99
|
-
(it) => it.nodes[0].nodes
|
|
100
|
-
.map(
|
|
101
|
-
(n) => n.attributes["class"] != null &&
|
|
102
|
-
n.attributes["class"]!.contains("crossreference")
|
|
103
|
-
? ""
|
|
104
|
-
: n.text,
|
|
105
|
-
)
|
|
106
|
-
.join(""),
|
|
107
|
-
)
|
|
108
|
-
.join("");
|
|
109
|
-
var els = document
|
|
110
|
-
.getElementsByTagName("span")
|
|
111
|
-
.where((it) => it.id.contains("en-NKJV"))
|
|
112
|
-
.where((it) => it.parent?.localName == "h3")
|
|
113
|
-
.map(
|
|
114
|
-
(it) =>
|
|
115
|
-
"${it.className.split(" ")[1]} ${it.nodes.map((n) => n.attributes["class"] != null && n.attributes["class"]!.contains("crossreference") ? "" : n.text).join("")}",
|
|
116
|
-
)
|
|
117
|
-
.map((it) {
|
|
118
|
-
final items = it.split(" ");
|
|
119
|
-
final vIndex = int.parse(items[0].split("-").last) - 1;
|
|
120
|
-
final heading = items.sublist(1).join(" ");
|
|
121
|
-
return Heading(vIndex, heading);
|
|
122
|
-
}).toList();
|
|
123
|
-
if (title != "") {
|
|
124
|
-
if (els.isNotEmpty) {
|
|
125
|
-
els.first.text = "$title<br>${els.first.text}";
|
|
126
|
-
} else {
|
|
127
|
-
els.add(Heading(0, title));
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return els;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
void main() async {
|
|
134
|
-
final engBooks = parseBible(
|
|
135
|
-
"English",
|
|
136
|
-
await File("./assets/bibles/English.txt").readAsString(),
|
|
137
|
-
);
|
|
138
|
-
print("starting");
|
|
139
|
-
// const bibleName = "English2";
|
|
140
|
-
// const outputFilename = "./assets/bibles/${bibleName}2.txt";
|
|
141
|
-
// if (File(outputFilename).existsSync()) {
|
|
142
|
-
// File(outputFilename).deleteSync();
|
|
143
|
-
// }
|
|
144
|
-
// final outputFile = File(outputFilename).openWrite();
|
|
145
|
-
// final bibleTxt = await File("./assets/bibles/$bibleName.txt").readAsString();
|
|
146
|
-
// final books = parseBible(bibleName, bibleTxt);
|
|
147
|
-
// for (var (bookIndex, book) in books.indexed) {
|
|
148
|
-
// for (var (chapterIndex, chapter) in book.chapters.indexed) {
|
|
149
|
-
// for (var (verseIndex, verse) in chapter.verses.indexed) {
|
|
150
|
-
// // print("$bookIndex $chapterIndex $verseIndex");
|
|
151
|
-
// final engVerses = engBooks[bookIndex].chapters[chapterIndex].verses;
|
|
152
|
-
// if (engVerses.length > verseIndex) {
|
|
153
|
-
// final engVerseHeading = engVerses[verseIndex].heading;
|
|
154
|
-
// if (engVerseHeading != "") {
|
|
155
|
-
// verse.heading = engVerseHeading;
|
|
156
|
-
// }
|
|
157
|
-
// }
|
|
158
|
-
// outputFile.writeln(
|
|
159
|
-
// "${book.index}|${chapter.index}|${verse.index}|${verse.heading}|${verse.text}",
|
|
160
|
-
// );
|
|
161
|
-
// }
|
|
162
|
-
// }
|
|
163
|
-
// }
|
|
164
|
-
// await outputFile.flush();
|
|
165
|
-
// await outputFile.close();
|
|
166
|
-
print("finished");
|
|
167
|
-
}
|
test/screenshot_test.dart
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import "package:async_redux/async_redux.dart";
|
|
2
2
|
import "package:flutter/material.dart";
|
|
3
3
|
import "package:flutter_test/flutter_test.dart";
|
|
4
|
+
import "package:go_router/go_router.dart";
|
|
4
5
|
import "package:golden_screenshot/golden_screenshot.dart";
|
|
5
6
|
import "package:only_bible_app/gen/bible.gen.dart";
|
|
6
7
|
import "package:only_bible_app/home.dart";
|
|
8
|
+
import "package:only_bible_app/store/app_navigator.dart";
|
|
7
9
|
import "package:only_bible_app/store/app_state.dart";
|
|
8
10
|
import "package:only_bible_app/theme.dart";
|
|
9
11
|
import "package:only_bible_app/utils.dart";
|
|
@@ -14,14 +16,24 @@ Widget buildScreenshotApp({
|
|
|
14
16
|
required Widget home,
|
|
15
17
|
ThemeMode themeMode = ThemeMode.light,
|
|
16
18
|
}) {
|
|
19
|
+
final router = GoRouter(
|
|
20
|
+
initialLocation: "/",
|
|
21
|
+
routes: [
|
|
22
|
+
GoRoute(path: "/", builder: (_, __) => home),
|
|
23
|
+
GoRoute(path: "/chapter/:bookIndex/:chapterIndex", builder: (_, __) => home),
|
|
24
|
+
],
|
|
25
|
+
);
|
|
17
26
|
return StoreProvider<AppState>(
|
|
18
27
|
store: store,
|
|
28
|
+
child: AppRouterScope(
|
|
29
|
+
router: router,
|
|
19
|
-
|
|
30
|
+
child: ScreenshotApp(
|
|
20
|
-
|
|
31
|
+
device: device,
|
|
21
|
-
|
|
32
|
+
theme: lightTheme,
|
|
22
|
-
|
|
33
|
+
darkTheme: darkTheme,
|
|
23
|
-
|
|
34
|
+
themeMode: themeMode,
|
|
24
|
-
|
|
35
|
+
home: home,
|
|
36
|
+
),
|
|
25
37
|
),
|
|
26
38
|
);
|
|
27
39
|
}
|
|
@@ -46,7 +58,7 @@ void main() {
|
|
|
46
58
|
await tester.pumpWidget(buildScreenshotApp(
|
|
47
59
|
device: device.device,
|
|
48
60
|
store: store,
|
|
49
|
-
home: const Home(),
|
|
61
|
+
home: const Home(bookIndex: 0, chapterIndex: 0),
|
|
50
62
|
));
|
|
51
63
|
await tester.loadAssets();
|
|
52
64
|
await tester.pump();
|
|
@@ -60,7 +72,7 @@ void main() {
|
|
|
60
72
|
await tester.pumpWidget(buildScreenshotApp(
|
|
61
73
|
device: device.device,
|
|
62
74
|
store: store,
|
|
63
|
-
home: const Home(),
|
|
75
|
+
home: const Home(bookIndex: 0, chapterIndex: 0),
|
|
64
76
|
themeMode: ThemeMode.dark,
|
|
65
77
|
));
|
|
66
78
|
await tester.loadAssets();
|
|
@@ -73,7 +85,7 @@ void main() {
|
|
|
73
85
|
await tester.pumpWidget(buildScreenshotApp(
|
|
74
86
|
device: device.device,
|
|
75
87
|
store: store,
|
|
76
|
-
home: const Home(),
|
|
88
|
+
home: const Home(bookIndex: 0, chapterIndex: 0),
|
|
77
89
|
));
|
|
78
90
|
await tester.pumpAndSettle();
|
|
79
91
|
await tester.tap(find.byKey(const Key("bookTitle")));
|
|
@@ -88,7 +100,7 @@ void main() {
|
|
|
88
100
|
await tester.pumpWidget(buildScreenshotApp(
|
|
89
101
|
device: device.device,
|
|
90
102
|
store: store,
|
|
91
|
-
home: const Home(),
|
|
103
|
+
home: const Home(bookIndex: 0, chapterIndex: 0),
|
|
92
104
|
));
|
|
93
105
|
await tester.pumpAndSettle();
|
|
94
106
|
await tester.tap(find.byKey(const Key("chapterTitle")));
|
|
@@ -115,7 +127,7 @@ void main() {
|
|
|
115
127
|
await tester.pumpWidget(buildScreenshotApp(
|
|
116
128
|
device: device.device,
|
|
117
129
|
store: store,
|
|
118
|
-
home: const Home(),
|
|
130
|
+
home: const Home(bookIndex: 0, chapterIndex: 0),
|
|
119
131
|
));
|
|
120
132
|
await tester.loadAssets();
|
|
121
133
|
await tester.pump();
|
|
@@ -127,7 +139,7 @@ void main() {
|
|
|
127
139
|
await tester.pumpWidget(buildScreenshotApp(
|
|
128
140
|
device: device.device,
|
|
129
141
|
store: store,
|
|
130
|
-
home: const Home(),
|
|
142
|
+
home: const Home(bookIndex: 0, chapterIndex: 0),
|
|
131
143
|
));
|
|
132
144
|
await tester.pumpAndSettle();
|
|
133
145
|
await tester.tap(find.byKey(const Key("settingsButton")));
|
test/widget_test.dart
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
// This is a basic Flutter widget test.
|
|
2
|
-
//
|
|
3
|
-
// To perform an interaction with a widget in your test, use the WidgetTester
|
|
4
|
-
// utility in the flutter_test package. For example, you can send tap and scroll
|
|
5
|
-
// gestures. You can also use WidgetTester to find child widgets in the widget
|
|
6
|
-
// tree, read text, and verify that the values of widget properties are correct.
|
|
7
|
-
|
|
8
|
-
import 'package:flutter/material.dart';
|
|
9
|
-
import 'package:flutter_test/flutter_test.dart';
|
|
10
|
-
|
|
11
|
-
import 'package:only_bible_app/main.dart';
|
|
12
|
-
|
|
13
|
-
void main() {
|
|
14
|
-
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
|
15
|
-
// Build our app and trigger a frame.
|
|
16
|
-
await tester.pumpWidget(const MyApp());
|
|
17
|
-
|
|
18
|
-
// Verify that our counter starts at 0.
|
|
19
|
-
expect(find.text('0'), findsOneWidget);
|
|
20
|
-
expect(find.text('1'), findsNothing);
|
|
21
|
-
|
|
22
|
-
// Tap the '+' icon and trigger a frame.
|
|
23
|
-
await tester.tap(find.byIcon(Icons.add));
|
|
24
|
-
await tester.pump();
|
|
25
|
-
|
|
26
|
-
// Verify that our counter has incremented.
|
|
27
|
-
expect(find.text('0'), findsNothing);
|
|
28
|
-
expect(find.text('1'), findsOneWidget);
|
|
29
|
-
});
|
|
30
|
-
}
|
tts.dart
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import "dart:io";
|
|
2
|
-
import "dart:isolate";
|
|
3
|
-
import "dart:typed_data";
|
|
4
|
-
|
|
5
|
-
import "package:flutter_soloud/flutter_soloud.dart";
|
|
6
|
-
import "package:flutter/services.dart";
|
|
7
|
-
import "package:path_provider/path_provider.dart";
|
|
8
|
-
import "package:sherpa_onnx/sherpa_onnx.dart" as sherpa_onnx;
|
|
9
|
-
|
|
10
|
-
String? _modelPath;
|
|
11
|
-
|
|
12
|
-
const _modelDir = "vits-piper-ne_NP-google-x_low";
|
|
13
|
-
|
|
14
|
-
Future<String> _copyAsset(String assetPath, String targetPath) async {
|
|
15
|
-
final file = File(targetPath);
|
|
16
|
-
if (!await file.exists() || file.lengthSync() == 0) {
|
|
17
|
-
final data = await rootBundle.load(assetPath);
|
|
18
|
-
await file.create(recursive: true);
|
|
19
|
-
await file.writeAsBytes(
|
|
20
|
-
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes),
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
return targetPath;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
Future<void> _ensureModelFiles() async {
|
|
27
|
-
if (_modelPath != null) return;
|
|
28
|
-
|
|
29
|
-
final dir = await getApplicationSupportDirectory();
|
|
30
|
-
_modelPath = p.join(dir.path, _modelDir);
|
|
31
|
-
|
|
32
|
-
final assetManifest = await AssetManifest.loadFromAssetBundle(rootBundle);
|
|
33
|
-
final allAssets = assetManifest.listAssets();
|
|
34
|
-
final modelAssets = allAssets.where((a) => a.startsWith("assets/$_modelDir/"));
|
|
35
|
-
|
|
36
|
-
for (final assetPath in modelAssets) {
|
|
37
|
-
final relativePath = assetPath.replaceFirst("assets/$_modelDir/", "");
|
|
38
|
-
await _copyAsset(assetPath, p.join(_modelPath!, relativePath));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
sherpa_onnx.initBindings();
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
void _generateInIsolate(List<dynamic> args) {
|
|
45
|
-
final sendPort = args[0] as SendPort;
|
|
46
|
-
final text = args[1] as String;
|
|
47
|
-
final modelPath = args[2] as String;
|
|
48
|
-
|
|
49
|
-
sherpa_onnx.initBindings();
|
|
50
|
-
|
|
51
|
-
final modelFile = p.join(modelPath, "ne_NP-chitwan-medium.onnx");
|
|
52
|
-
final tokensFile = p.join(modelPath, "tokens.txt");
|
|
53
|
-
final dataDir = p.join(modelPath, "espeak-ng-data");
|
|
54
|
-
print("model exists: ${File(modelFile).existsSync()} $modelFile");
|
|
55
|
-
print("tokens exists: ${File(tokensFile).existsSync()} $tokensFile");
|
|
56
|
-
print("dataDir exists: ${Directory(dataDir).existsSync()} $dataDir");
|
|
57
|
-
|
|
58
|
-
final config = sherpa_onnx.OfflineTtsConfig(
|
|
59
|
-
model: sherpa_onnx.OfflineTtsModelConfig(
|
|
60
|
-
vits: sherpa_onnx.OfflineTtsVitsModelConfig(
|
|
61
|
-
model: modelFile,
|
|
62
|
-
tokens: tokensFile,
|
|
63
|
-
dataDir: dataDir,
|
|
64
|
-
),
|
|
65
|
-
numThreads: 2,
|
|
66
|
-
debug: true,
|
|
67
|
-
provider: "cpu",
|
|
68
|
-
),
|
|
69
|
-
maxNumSenetences: 1,
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
final tts = sherpa_onnx.OfflineTts(config);
|
|
73
|
-
|
|
74
|
-
tts.generateWithConfig(
|
|
75
|
-
text: text,
|
|
76
|
-
config: const sherpa_onnx.OfflineTtsGenerationConfig(
|
|
77
|
-
speed: 1.0,
|
|
78
|
-
),
|
|
79
|
-
onProgress: (samples, progress) {
|
|
80
|
-
sendPort.send(Float32List.fromList(samples));
|
|
81
|
-
return 1;
|
|
82
|
-
},
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
tts.free();
|
|
86
|
-
sendPort.send(null);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
Future<void> runTTS(String text) async {
|
|
90
|
-
await _ensureModelFiles();
|
|
91
|
-
|
|
92
|
-
final stream = SoLoud.instance.setBufferStream(
|
|
93
|
-
bufferingType: BufferingType.preserved,
|
|
94
|
-
format: BufferType.f32le,
|
|
95
|
-
channels: Channels.mono,
|
|
96
|
-
sampleRate: 24000,
|
|
97
|
-
bufferingTimeNeeds: 0.5,
|
|
98
|
-
onBuffering: (isBuffering, handle, time) {},
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
final handle = await SoLoud.instance.play(stream);
|
|
102
|
-
|
|
103
|
-
final receivePort = ReceivePort();
|
|
104
|
-
await Isolate.spawn(
|
|
105
|
-
_generateInIsolate,
|
|
106
|
-
[
|
|
107
|
-
receivePort.sendPort,
|
|
108
|
-
text,
|
|
109
|
-
_modelPath!,
|
|
110
|
-
],
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
await for (final message in receivePort) {
|
|
114
|
-
if (message == null) {
|
|
115
|
-
SoLoud.instance.setDataIsEnded(stream);
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
|
-
final samples = message as Float32List;
|
|
119
|
-
SoLoud.instance.addAudioDataStream(stream, samples.buffer.asUint8List());
|
|
120
|
-
}
|
|
121
|
-
receivePort.close();
|
|
122
|
-
|
|
123
|
-
await stream.soundEvents.firstWhere(
|
|
124
|
-
(e) => e.event == SoundEventType.handleIsNoMoreValid && e.handle == handle,
|
|
125
|
-
);
|
|
126
|
-
await SoLoud.instance.disposeSource(stream);
|
|
127
|
-
}
|