~repos /only-bible-app

#kotlin#android#ios

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

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


Files changed (51) hide show
  1. .gitignore +2 -1
  2. assets/bibles/{bn.bin → bn.bin.gz} +0 -0
  3. assets/bibles/{en_asv.bin → en_asv.bin.gz} +0 -0
  4. assets/bibles/{en_bsb.bin → en_bsb.bin.gz} +0 -0
  5. assets/bibles/{en_kjv.bin → en_kjv.bin.gz} +0 -0
  6. assets/bibles/{en_net.bin → en_net.bin.gz} +0 -0
  7. assets/bibles/{gu.bin → gu.bin.gz} +0 -0
  8. assets/bibles/{hi.bin → hi.bin.gz} +0 -0
  9. assets/bibles/{ja.bin → ja.bin.gz} +0 -0
  10. assets/bibles/{kn.bin → kn.bin.gz} +0 -0
  11. assets/bibles/{ml.bin → ml.bin.gz} +0 -0
  12. assets/bibles/{ne.bin → ne.bin.gz} +0 -0
  13. assets/bibles/{or.bin → or.bin.gz} +0 -0
  14. assets/bibles/{pa.bin → pa.bin.gz} +0 -0
  15. assets/bibles/{ta.bin → ta.bin.gz} +0 -0
  16. assets/bibles/{te.bin → te.bin.gz} +0 -0
  17. lib/dialog.dart +4 -2
  18. lib/home.dart +10 -5
  19. lib/store/actions_state.dart +6 -2
  20. lib/store/app_state.dart +11 -14
  21. lib/theme.dart +72 -1
  22. lib/utils.dart +1 -1
  23. lib/widgets/book_select_sheet.dart +0 -8
  24. lib/widgets/book_tile.dart +7 -4
  25. lib/widgets/home_app_bar.dart +26 -21
  26. lib/widgets/settings_sheet.dart +31 -18
  27. pubspec.lock +8 -0
  28. pubspec.yaml +1 -0
  29. screenshots/ipadScreenshots/book_select.png +0 -0
  30. screenshots/ipadScreenshots/chapter_select.png +0 -0
  31. screenshots/ipadScreenshots/home_dark.png +0 -0
  32. screenshots/ipadScreenshots/home_light.png +0 -0
  33. screenshots/ipadScreenshots/settings.png +0 -0
  34. screenshots/ipadScreenshots/verse_select.png +0 -0
  35. screenshots/iphoneScreenshots/book_select.png +0 -0
  36. screenshots/iphoneScreenshots/chapter_select.png +0 -0
  37. screenshots/iphoneScreenshots/home_dark.png +0 -0
  38. screenshots/iphoneScreenshots/home_light.png +0 -0
  39. screenshots/iphoneScreenshots/settings.png +0 -0
  40. screenshots/iphoneScreenshots/verse_select.png +0 -0
  41. screenshots/phoneScreenshots/book_select.png +0 -0
  42. screenshots/phoneScreenshots/chapter_select.png +0 -0
  43. screenshots/phoneScreenshots/home_dark.png +0 -0
  44. screenshots/phoneScreenshots/home_light.png +0 -0
  45. screenshots/phoneScreenshots/settings.png +0 -0
  46. screenshots/phoneScreenshots/verse_select.png +0 -0
  47. scripts/generate_flatbuffers.dart +1 -2
  48. scripts/scrape.dart +0 -167
  49. test/screenshot_test.dart +24 -12
  50. test/widget_test.dart +0 -30
  51. 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) ? Text(item.languageEnglish) : null,
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 (boldFont, fontSize, selectedVerses, _, _) =
61
+ final (fontWeight, fontSize, selectedVerses, _, _) =
62
- context.select((s) => (s.boldFont, s.fontSize, s.selectedVerses, s.highlights, s.darkMode));
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: boldFont ? FontWeight.w500 : FontWeight.w400,
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(context, bible, v.heading!, theme.labelLarge!),
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, false),
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 ToggleBoldFontAction extends ReduxAction<AppState> {
16
+ class UpdateFontWeightAction extends ReduxAction<AppState> {
17
+ final int value;
18
+
19
+ UpdateFontWeightAction(this.value);
20
+
17
21
  @override
18
- AppState reduce() => state.copy(boldFont: !state.boldFont);
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 bool boldFont;
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.boldFont = false,
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
- bool? boldFont,
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
- boldFont: boldFont ?? this.boldFont,
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
- "boldFont": boldFont,
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
- boldFont: json["boldFont"] as bool? ?? false,
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, bool heading) {
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 (!heading && isSelected) {
93
+ if (isSelected) {
95
94
  return TextStyle(
96
- backgroundColor: darkMode ? Colors.grey.shade800 : Colors.grey.shade200,
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: highlight != null ? Colors.white : Theme.of(context).colorScheme.onSurface,
100
+ color: getHighlight(v) ?? Theme.of(context).colorScheme.onSurface,
104
101
  );
105
102
  }
106
103
  return TextStyle(
107
- backgroundColor: highlight ?? Theme.of(context).colorScheme.surface,
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: const Color(0xAAF8D0DC),
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 : colorScheme.surface,
18
+ color: isSelected ? colorScheme.primaryContainer : tileColor,
16
- shadowColor: colorScheme.scrim,
19
+ shadowColor: colorScheme.shadow,
17
20
  shape: RoundedRectangleBorder(
18
21
  borderRadius: BorderRadius.circular(12),
19
22
  side: BorderSide(
20
- color: isSelected ? colorScheme.primary : colorScheme.scrim,
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
- child: InkWell(
35
+ InkWell(
37
- key: const Key("bookTitle"),
36
+ key: const Key("bookTitle"),
38
- enableFeedback: true,
37
+ enableFeedback: true,
39
- onTap: () => context.dispatch(ShowBookSelectAction(context, context.router, bible)),
38
+ onTap: () => context.dispatch(ShowBookSelectAction(context, context.router, bible)),
40
- child: Text(
39
+ child: Text(
41
- book.name!,
40
+ book.name!,
42
- style: Theme.of(context).textTheme.headlineMedium,
41
+ style: Theme.of(context).textTheme.headlineMedium,
43
- overflow: TextOverflow.ellipsis,
44
- maxLines: 1,
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
- InkWell(
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
- key: const Key("bible"),
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 boldFont = context.select((s) => s.boldFont);
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
- color: colorScheme.surface,
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: colorScheme.scrim),
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
- color: colorScheme.surface,
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: colorScheme.scrim),
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
- // Bold font toggle
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
- color: colorScheme.surface,
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: colorScheme.scrim),
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
- Expanded(child: Text(bible.boldFontTitle!)),
119
+ Text("$fontWeight"),
114
- Switch(
120
+ Expanded(
121
+ child: Slider(
115
- value: boldFont,
122
+ value: fontWeight.toDouble(),
123
+ min: 100,
124
+ max: 900,
125
+ divisions: 8,
126
+ label: "$fontWeight",
116
- onChanged: (_) => context.dispatch(ToggleBoldFontAction()),
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
- color: colorScheme.surface,
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: colorScheme.scrim),
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
- child: ScreenshotApp(
30
+ child: ScreenshotApp(
20
- device: device,
31
+ device: device,
21
- theme: lightTheme,
32
+ theme: lightTheme,
22
- darkTheme: darkTheme,
33
+ darkTheme: darkTheme,
23
- themeMode: themeMode,
34
+ themeMode: themeMode,
24
- home: home,
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
- }