~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.
e545d60e
—
pyrossh 1 month ago
update stuff
- lib/app.dart +19 -17
- lib/dialog.dart +4 -3
- lib/gen/bible.gen.dart +30 -28
- lib/main.dart +13 -34
- lib/screens/bible_select_screen.dart +4 -7
- lib/screens/book_select_screen.dart +3 -9
- lib/screens/chapter_select_screen.dart +3 -7
- lib/screens/chapter_view_screen.dart +0 -6
- lib/sheets/actions_sheet.dart +12 -6
- lib/sheets/settings_sheet.dart +7 -7
- lib/store/actions.dart +351 -24
- lib/store/app_navigator.dart +9 -248
- lib/store/app_persistor.dart +5 -2
- lib/store/app_state.dart +5 -50
- lib/utils.dart +1 -3
- lib/widgets/chapter_app_bar.dart +4 -3
- lib/widgets/verses_view.dart +52 -44
lib/app.dart
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import "package:async_redux/async_redux.dart" show Store;
|
|
1
|
+
import "package:async_redux/async_redux.dart" show Store, StoreProvider;
|
|
2
2
|
import "package:flutter/material.dart";
|
|
3
3
|
import "package:go_router/go_router.dart";
|
|
4
4
|
import "package:only_bible_app/gen/l10n/app_localizations.dart";
|
|
@@ -7,6 +7,7 @@ import "package:only_bible_app/screens/book_select_screen.dart";
|
|
|
7
7
|
import "package:only_bible_app/screens/chapter_select_screen.dart";
|
|
8
8
|
import "package:only_bible_app/screens/chapter_view_screen.dart";
|
|
9
9
|
import "package:only_bible_app/screens/webview_screen.dart";
|
|
10
|
+
import "package:only_bible_app/store/actions.dart";
|
|
10
11
|
import "package:only_bible_app/store/app_navigator.dart";
|
|
11
12
|
import "package:only_bible_app/store/app_state.dart";
|
|
12
13
|
import "package:only_bible_app/theme.dart";
|
|
@@ -15,17 +16,16 @@ import "package:only_bible_app/utils.dart";
|
|
|
15
16
|
class App extends StatelessWidget {
|
|
16
17
|
final GlobalKey<NavigatorState> globalNavigatorKey;
|
|
17
18
|
final Store<AppState> store;
|
|
18
|
-
final AppNavigator navigator;
|
|
19
19
|
late final GoRouter _router;
|
|
20
20
|
|
|
21
|
-
App({super.key, required this.globalNavigatorKey, required this.store
|
|
21
|
+
App({super.key, required this.globalNavigatorKey, required this.store}) {
|
|
22
22
|
final s = store.state;
|
|
23
23
|
_router = GoRouter(
|
|
24
24
|
navigatorKey: globalNavigatorKey,
|
|
25
25
|
initialLocation:
|
|
26
26
|
s.firstOpen ? "/bible" : "/chapter/${Uri.encodeComponent(s.bibleName)}/${s.savedBook}/${s.savedChapter}",
|
|
27
27
|
redirect: (context, state) {
|
|
28
|
-
|
|
28
|
+
store.dispatch(HideActionsAction());
|
|
29
29
|
return null;
|
|
30
30
|
},
|
|
31
31
|
routes: [
|
|
@@ -107,20 +107,22 @@ class App extends StatelessWidget {
|
|
|
107
107
|
|
|
108
108
|
@override
|
|
109
109
|
Widget build(BuildContext context) {
|
|
110
|
-
return
|
|
110
|
+
return StoreProvider<AppState>(
|
|
111
111
|
store: store,
|
|
112
|
+
child: AppRouterScope(
|
|
112
|
-
|
|
113
|
+
router: _router,
|
|
113
|
-
|
|
114
|
+
child: Builder(
|
|
114
|
-
|
|
115
|
+
builder: (context) => MaterialApp.router(
|
|
115
|
-
|
|
116
|
+
routerConfig: _router,
|
|
116
|
-
|
|
117
|
+
onGenerateTitle: (context) => context.l.title,
|
|
117
|
-
|
|
118
|
+
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
118
|
-
|
|
119
|
+
supportedLocales: AppLocalizations.supportedLocales,
|
|
119
|
-
|
|
120
|
+
debugShowCheckedModeBanner: false,
|
|
120
|
-
|
|
121
|
+
themeMode: context.select((s) => s.darkMode) ? ThemeMode.dark : ThemeMode.light,
|
|
121
|
-
|
|
122
|
+
theme: lightTheme,
|
|
122
|
-
|
|
123
|
+
darkTheme: darkTheme,
|
|
123
|
-
|
|
124
|
+
locale: Locale(context.select((s) => s.languageCode)),
|
|
125
|
+
),
|
|
124
126
|
),
|
|
125
127
|
),
|
|
126
128
|
);
|
lib/dialog.dart
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import "dart:ui";
|
|
2
2
|
import "package:flutter/material.dart";
|
|
3
|
-
import "package:only_bible_app/
|
|
3
|
+
import "package:only_bible_app/store/actions.dart";
|
|
4
4
|
import "package:only_bible_app/store/app_navigator.dart";
|
|
5
|
+
import "package:only_bible_app/utils.dart";
|
|
5
6
|
|
|
6
7
|
void showAlert(BuildContext context, String title, String message) {
|
|
7
8
|
showDialog(
|
|
@@ -50,13 +51,13 @@ void showReportError(BuildContext context, String message, StackTrace? st) {
|
|
|
50
51
|
TextButton(
|
|
51
52
|
onPressed: () {
|
|
52
53
|
recordError(message, st);
|
|
53
|
-
context.
|
|
54
|
+
context.dispatch(ChangeBibleAction(context.router));
|
|
54
55
|
},
|
|
55
56
|
child: const Text("Yes"),
|
|
56
57
|
),
|
|
57
58
|
TextButton(
|
|
58
59
|
onPressed: () {
|
|
59
|
-
context.
|
|
60
|
+
context.dispatch(ChangeBibleAction(context.router));
|
|
60
61
|
},
|
|
61
62
|
child: const Text("No"),
|
|
62
63
|
),
|
lib/gen/bible.gen.dart
CHANGED
|
@@ -6,6 +6,8 @@ library only_bible_app.bible;
|
|
|
6
6
|
import 'dart:typed_data' show Uint8List;
|
|
7
7
|
import 'package:flat_buffers/flat_buffers.dart' as fb;
|
|
8
8
|
|
|
9
|
+
|
|
10
|
+
|
|
9
11
|
class Verse {
|
|
10
12
|
Verse._(this._bc, this._bcOffset);
|
|
11
13
|
factory Verse(List<int> bytes) {
|
|
@@ -34,7 +36,8 @@ class _VerseReader extends fb.TableReader<Verse> {
|
|
|
34
36
|
const _VerseReader();
|
|
35
37
|
|
|
36
38
|
@override
|
|
37
|
-
Verse createObject(fb.BufferContext bc, int offset) =>
|
|
39
|
+
Verse createObject(fb.BufferContext bc, int offset) =>
|
|
40
|
+
Verse._(bc, offset);
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
class VerseBuilder {
|
|
@@ -50,22 +53,18 @@ class VerseBuilder {
|
|
|
50
53
|
fbBuilder.addInt32(0, index);
|
|
51
54
|
return fbBuilder.offset;
|
|
52
55
|
}
|
|
53
|
-
|
|
54
56
|
int addBook(int? book) {
|
|
55
57
|
fbBuilder.addInt32(1, book);
|
|
56
58
|
return fbBuilder.offset;
|
|
57
59
|
}
|
|
58
|
-
|
|
59
60
|
int addChapter(int? chapter) {
|
|
60
61
|
fbBuilder.addInt32(2, chapter);
|
|
61
62
|
return fbBuilder.offset;
|
|
62
63
|
}
|
|
63
|
-
|
|
64
64
|
int addHeadingOffset(int? offset) {
|
|
65
65
|
fbBuilder.addOffset(3, offset);
|
|
66
66
|
return fbBuilder.offset;
|
|
67
67
|
}
|
|
68
|
-
|
|
69
68
|
int addTextOffset(int? offset) {
|
|
70
69
|
fbBuilder.addOffset(4, offset);
|
|
71
70
|
return fbBuilder.offset;
|
|
@@ -89,7 +88,8 @@ class VerseObjectBuilder extends fb.ObjectBuilder {
|
|
|
89
88
|
int? chapter,
|
|
90
89
|
String? heading,
|
|
91
90
|
String? text,
|
|
91
|
+
})
|
|
92
|
-
|
|
92
|
+
: _index = index,
|
|
93
93
|
_book = book,
|
|
94
94
|
_chapter = chapter,
|
|
95
95
|
_heading = heading,
|
|
@@ -98,8 +98,10 @@ class VerseObjectBuilder extends fb.ObjectBuilder {
|
|
|
98
98
|
/// Finish building, and store into the [fbBuilder].
|
|
99
99
|
@override
|
|
100
100
|
int finish(fb.Builder fbBuilder) {
|
|
101
|
-
final int? headingOffset = _heading == null ? null
|
|
101
|
+
final int? headingOffset = _heading == null ? null
|
|
102
|
+
: fbBuilder.writeString(_heading!);
|
|
102
|
-
final int? textOffset = _text == null ? null
|
|
103
|
+
final int? textOffset = _text == null ? null
|
|
104
|
+
: fbBuilder.writeString(_text!);
|
|
103
105
|
fbBuilder.startTable(5);
|
|
104
106
|
fbBuilder.addInt32(0, _index);
|
|
105
107
|
fbBuilder.addInt32(1, _book);
|
|
@@ -117,7 +119,6 @@ class VerseObjectBuilder extends fb.ObjectBuilder {
|
|
|
117
119
|
return fbBuilder.buffer;
|
|
118
120
|
}
|
|
119
121
|
}
|
|
120
|
-
|
|
121
122
|
class Chapter {
|
|
122
123
|
Chapter._(this._bc, this._bcOffset);
|
|
123
124
|
factory Chapter(List<int> bytes) {
|
|
@@ -144,7 +145,8 @@ class _ChapterReader extends fb.TableReader<Chapter> {
|
|
|
144
145
|
const _ChapterReader();
|
|
145
146
|
|
|
146
147
|
@override
|
|
147
|
-
Chapter createObject(fb.BufferContext bc, int offset) =>
|
|
148
|
+
Chapter createObject(fb.BufferContext bc, int offset) =>
|
|
149
|
+
Chapter._(bc, offset);
|
|
148
150
|
}
|
|
149
151
|
|
|
150
152
|
class ChapterBuilder {
|
|
@@ -160,12 +162,10 @@ class ChapterBuilder {
|
|
|
160
162
|
fbBuilder.addInt32(0, index);
|
|
161
163
|
return fbBuilder.offset;
|
|
162
164
|
}
|
|
163
|
-
|
|
164
165
|
int addBook(int? book) {
|
|
165
166
|
fbBuilder.addInt32(1, book);
|
|
166
167
|
return fbBuilder.offset;
|
|
167
168
|
}
|
|
168
|
-
|
|
169
169
|
int addVersesOffset(int? offset) {
|
|
170
170
|
fbBuilder.addOffset(2, offset);
|
|
171
171
|
return fbBuilder.offset;
|
|
@@ -185,15 +185,16 @@ class ChapterObjectBuilder extends fb.ObjectBuilder {
|
|
|
185
185
|
int? index,
|
|
186
186
|
int? book,
|
|
187
187
|
List<VerseObjectBuilder>? verses,
|
|
188
|
+
})
|
|
188
|
-
|
|
189
|
+
: _index = index,
|
|
189
190
|
_book = book,
|
|
190
191
|
_verses = verses;
|
|
191
192
|
|
|
192
193
|
/// Finish building, and store into the [fbBuilder].
|
|
193
194
|
@override
|
|
194
195
|
int finish(fb.Builder fbBuilder) {
|
|
195
|
-
final int? versesOffset =
|
|
196
|
+
final int? versesOffset = _verses == null ? null
|
|
196
|
-
|
|
197
|
+
: fbBuilder.writeList(_verses!.map((b) => b.getOrCreateOffset(fbBuilder)).toList());
|
|
197
198
|
fbBuilder.startTable(3);
|
|
198
199
|
fbBuilder.addInt32(0, _index);
|
|
199
200
|
fbBuilder.addInt32(1, _book);
|
|
@@ -209,7 +210,6 @@ class ChapterObjectBuilder extends fb.ObjectBuilder {
|
|
|
209
210
|
return fbBuilder.buffer;
|
|
210
211
|
}
|
|
211
212
|
}
|
|
212
|
-
|
|
213
213
|
class Book {
|
|
214
214
|
Book._(this._bc, this._bcOffset);
|
|
215
215
|
factory Book(List<int> bytes) {
|
|
@@ -235,7 +235,8 @@ class _BookReader extends fb.TableReader<Book> {
|
|
|
235
235
|
const _BookReader();
|
|
236
236
|
|
|
237
237
|
@override
|
|
238
|
-
Book createObject(fb.BufferContext bc, int offset) =>
|
|
238
|
+
Book createObject(fb.BufferContext bc, int offset) =>
|
|
239
|
+
Book._(bc, offset);
|
|
239
240
|
}
|
|
240
241
|
|
|
241
242
|
class BookBuilder {
|
|
@@ -251,7 +252,6 @@ class BookBuilder {
|
|
|
251
252
|
fbBuilder.addInt32(0, index);
|
|
252
253
|
return fbBuilder.offset;
|
|
253
254
|
}
|
|
254
|
-
|
|
255
255
|
int addChaptersOffset(int? offset) {
|
|
256
256
|
fbBuilder.addOffset(1, offset);
|
|
257
257
|
return fbBuilder.offset;
|
|
@@ -269,14 +269,15 @@ class BookObjectBuilder extends fb.ObjectBuilder {
|
|
|
269
269
|
BookObjectBuilder({
|
|
270
270
|
int? index,
|
|
271
271
|
List<ChapterObjectBuilder>? chapters,
|
|
272
|
+
})
|
|
272
|
-
|
|
273
|
+
: _index = index,
|
|
273
274
|
_chapters = chapters;
|
|
274
275
|
|
|
275
276
|
/// Finish building, and store into the [fbBuilder].
|
|
276
277
|
@override
|
|
277
278
|
int finish(fb.Builder fbBuilder) {
|
|
278
|
-
final int? chaptersOffset =
|
|
279
|
+
final int? chaptersOffset = _chapters == null ? null
|
|
279
|
-
|
|
280
|
+
: fbBuilder.writeList(_chapters!.map((b) => b.getOrCreateOffset(fbBuilder)).toList());
|
|
280
281
|
fbBuilder.startTable(2);
|
|
281
282
|
fbBuilder.addInt32(0, _index);
|
|
282
283
|
fbBuilder.addOffset(1, chaptersOffset);
|
|
@@ -291,7 +292,6 @@ class BookObjectBuilder extends fb.ObjectBuilder {
|
|
|
291
292
|
return fbBuilder.buffer;
|
|
292
293
|
}
|
|
293
294
|
}
|
|
294
|
-
|
|
295
295
|
class Bible {
|
|
296
296
|
Bible._(this._bc, this._bcOffset);
|
|
297
297
|
factory Bible(List<int> bytes) {
|
|
@@ -317,7 +317,8 @@ class _BibleReader extends fb.TableReader<Bible> {
|
|
|
317
317
|
const _BibleReader();
|
|
318
318
|
|
|
319
319
|
@override
|
|
320
|
-
Bible createObject(fb.BufferContext bc, int offset) =>
|
|
320
|
+
Bible createObject(fb.BufferContext bc, int offset) =>
|
|
321
|
+
Bible._(bc, offset);
|
|
321
322
|
}
|
|
322
323
|
|
|
323
324
|
class BibleBuilder {
|
|
@@ -333,7 +334,6 @@ class BibleBuilder {
|
|
|
333
334
|
fbBuilder.addOffset(0, offset);
|
|
334
335
|
return fbBuilder.offset;
|
|
335
336
|
}
|
|
336
|
-
|
|
337
337
|
int addBooksOffset(int? offset) {
|
|
338
338
|
fbBuilder.addOffset(1, offset);
|
|
339
339
|
return fbBuilder.offset;
|
|
@@ -351,15 +351,17 @@ class BibleObjectBuilder extends fb.ObjectBuilder {
|
|
|
351
351
|
BibleObjectBuilder({
|
|
352
352
|
String? name,
|
|
353
353
|
List<BookObjectBuilder>? books,
|
|
354
|
+
})
|
|
354
|
-
|
|
355
|
+
: _name = name,
|
|
355
356
|
_books = books;
|
|
356
357
|
|
|
357
358
|
/// Finish building, and store into the [fbBuilder].
|
|
358
359
|
@override
|
|
359
360
|
int finish(fb.Builder fbBuilder) {
|
|
360
|
-
final int? nameOffset = _name == null ? null
|
|
361
|
+
final int? nameOffset = _name == null ? null
|
|
362
|
+
: fbBuilder.writeString(_name!);
|
|
361
|
-
final int? booksOffset =
|
|
363
|
+
final int? booksOffset = _books == null ? null
|
|
362
|
-
|
|
364
|
+
: fbBuilder.writeList(_books!.map((b) => b.getOrCreateOffset(fbBuilder)).toList());
|
|
363
365
|
fbBuilder.startTable(2);
|
|
364
366
|
fbBuilder.addOffset(0, nameOffset);
|
|
365
367
|
fbBuilder.addOffset(1, booksOffset);
|
lib/main.dart
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import "package:flutter/material.dart";
|
|
2
2
|
import "package:flutter/services.dart";
|
|
3
3
|
import "package:flutter/foundation.dart";
|
|
4
|
-
import "package:flutter/scheduler.dart";
|
|
5
4
|
import "package:flutter_azure_tts/flutter_azure_tts.dart";
|
|
6
5
|
import "package:flutter_web_plugins/url_strategy.dart";
|
|
7
6
|
import "package:flutter_native_splash/flutter_native_splash.dart";
|
|
8
7
|
import "package:async_redux/async_redux.dart";
|
|
9
8
|
import "package:only_bible_app/app.dart";
|
|
10
|
-
|
|
9
|
+
|
|
11
|
-
import "package:only_bible_app/store/app_navigator.dart";
|
|
12
|
-
import "package:only_bible_app/store/actions.dart";
|
|
13
10
|
import "package:only_bible_app/store/app_persistor.dart";
|
|
14
11
|
import "package:only_bible_app/store/app_state.dart";
|
|
12
|
+
import "package:only_bible_app/utils.dart";
|
|
15
13
|
|
|
16
14
|
void updateStatusBar(bool v) {
|
|
17
15
|
if (v) {
|
|
@@ -36,45 +34,26 @@ void updateStatusBar(bool v) {
|
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
void main() async {
|
|
39
|
-
final globalNavigatorKey = GlobalKey<NavigatorState>();
|
|
40
|
-
final persistor = AppPersistor();
|
|
41
|
-
final initialState = await persistor.readState();
|
|
42
|
-
final store = Store<AppState>(
|
|
43
|
-
initialState: initialState ?? const AppState(),
|
|
44
|
-
persistor: persistor,
|
|
45
|
-
);
|
|
46
|
-
// FlutterError.onError = (errorDetails) {
|
|
47
|
-
// SchedulerBinding.instance.addPostFrameCallback((d) {
|
|
48
|
-
// showReportError(
|
|
49
|
-
// globalNavigatorKey.currentState!.context,
|
|
50
|
-
// errorDetails.exception.toString(),
|
|
51
|
-
// errorDetails.stack,
|
|
52
|
-
// );
|
|
53
|
-
// });
|
|
54
|
-
// };
|
|
55
|
-
// PlatformDispatcher.instance.onError = (error, stack) {
|
|
56
|
-
// Future.delayed(const Duration(seconds: 1), () {
|
|
57
|
-
// showReportError(
|
|
58
|
-
// globalNavigatorKey.currentState!.context,
|
|
59
|
-
// error.toString(),
|
|
60
|
-
// stack,
|
|
61
|
-
// );
|
|
62
|
-
// });
|
|
63
|
-
// return true;
|
|
64
|
-
// };
|
|
65
37
|
FlutterNativeSplash.preserve(
|
|
66
38
|
widgetsBinding: WidgetsFlutterBinding.ensureInitialized(),
|
|
67
39
|
);
|
|
68
40
|
usePathUrlStrategy();
|
|
69
|
-
WidgetsFlutterBinding.ensureInitialized();
|
|
70
41
|
FlutterAzureTts.init(
|
|
71
42
|
subscriptionKey: "a9d2d78796924a2a9df2b6d5c1c4a576",
|
|
72
43
|
region: "centralindia",
|
|
73
44
|
withLogs: true,
|
|
74
45
|
);
|
|
46
|
+
final globalNavigatorKey = GlobalKey<NavigatorState>();
|
|
47
|
+
final persistor = AppPersistor();
|
|
48
|
+
final json = await persistor.readJson();
|
|
49
|
+
final bibleName = json?["bibleName"] as String? ?? "English";
|
|
50
|
+
final bible = await loadBible(bibleName);
|
|
51
|
+
final initialState = json != null ? AppState.fromJson(json, bible) : AppState(bible: bible);
|
|
52
|
+
final store = Store<AppState>(
|
|
53
|
+
initialState: initialState,
|
|
54
|
+
persistor: persistor,
|
|
55
|
+
);
|
|
75
56
|
updateStatusBar(store.state.darkMode);
|
|
76
|
-
await store.dispatchAndWait(LoadBibleAction());
|
|
77
|
-
final navigator = AppNavigator(store);
|
|
78
|
-
runApp(App(globalNavigatorKey: globalNavigatorKey, store: store
|
|
57
|
+
runApp(App(globalNavigatorKey: globalNavigatorKey, store: store));
|
|
79
58
|
FlutterNativeSplash.remove();
|
|
80
59
|
}
|
lib/screens/bible_select_screen.dart
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "package:flutter/material.dart";
|
|
2
|
-
import "package:only_bible_app/store/app_navigator.dart";
|
|
3
2
|
import "package:only_bible_app/store/actions.dart";
|
|
3
|
+
import "package:only_bible_app/store/app_navigator.dart";
|
|
4
4
|
import "package:only_bible_app/utils.dart";
|
|
5
5
|
import "package:only_bible_app/widgets/scaffold_menu.dart";
|
|
6
6
|
import "package:only_bible_app/widgets/sliver_heading.dart";
|
|
@@ -38,12 +38,9 @@ class BibleSelectScreen extends StatelessWidget {
|
|
|
38
38
|
if (s.firstOpen) {
|
|
39
39
|
context.dispatch(FirstOpenDoneAction());
|
|
40
40
|
}
|
|
41
|
+
context.dispatch(
|
|
41
|
-
|
|
42
|
+
UpdateCurrentBibleAction(
|
|
42
|
-
context,
|
|
43
|
-
|
|
43
|
+
context.router, l.languageTitle, l.localeName, s.savedBook, s.savedChapter),
|
|
44
|
-
l.localeName,
|
|
45
|
-
s.savedBook,
|
|
46
|
-
s.savedChapter,
|
|
47
44
|
);
|
|
48
45
|
},
|
|
49
46
|
);
|
lib/screens/book_select_screen.dart
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "package:flutter/material.dart";
|
|
2
|
-
import "package:
|
|
2
|
+
import "package:only_bible_app/store/actions.dart";
|
|
3
3
|
import "package:only_bible_app/store/app_navigator.dart";
|
|
4
4
|
import "package:only_bible_app/utils.dart";
|
|
5
5
|
import "package:only_bible_app/widgets/scaffold_menu.dart";
|
|
@@ -15,20 +15,14 @@ class BookSelectScreen extends StatelessWidget {
|
|
|
15
15
|
dynamic onBookSelected(BuildContext context, Bible bible, int index) {
|
|
16
16
|
final book = bible.books![index];
|
|
17
17
|
if (book.chapters!.length == 1) {
|
|
18
|
-
return context.
|
|
18
|
+
return context.dispatch(ReplaceBookChapterAction(context.router, bible.name!, index, 0));
|
|
19
19
|
}
|
|
20
|
-
context.
|
|
20
|
+
context.dispatch(ChangeChapterAction(context.router, bible, book));
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
@override
|
|
24
24
|
Widget build(BuildContext context) {
|
|
25
25
|
final bible = context.select((s) => s.bible);
|
|
26
|
-
if (bible == null) {
|
|
27
|
-
return ColoredBox(
|
|
28
|
-
color: Theme.of(context).colorScheme.surface,
|
|
29
|
-
child: const Center(child: CircularProgressIndicator()),
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
26
|
return ScaffoldMenu(
|
|
33
27
|
child: CustomScrollView(
|
|
34
28
|
physics: const BouncingScrollPhysics(),
|
lib/screens/chapter_select_screen.dart
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import "package:flutter/material.dart";
|
|
2
|
+
import "package:only_bible_app/store/actions.dart";
|
|
2
3
|
import "package:only_bible_app/store/app_navigator.dart";
|
|
3
4
|
import "package:only_bible_app/utils.dart";
|
|
4
5
|
import "package:only_bible_app/widgets/scaffold_menu.dart";
|
|
@@ -14,12 +15,6 @@ class ChapterSelectScreen extends StatelessWidget {
|
|
|
14
15
|
@override
|
|
15
16
|
Widget build(BuildContext context) {
|
|
16
17
|
final bible = context.select((s) => s.bible);
|
|
17
|
-
if (bible == null) {
|
|
18
|
-
return ColoredBox(
|
|
19
|
-
color: Theme.of(context).colorScheme.surface,
|
|
20
|
-
child: const Center(child: CircularProgressIndicator()),
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
18
|
final book = bible.books![bookIndex];
|
|
24
19
|
return ScaffoldMenu(
|
|
25
20
|
child: CustomScrollView(
|
|
@@ -30,7 +25,8 @@ class ChapterSelectScreen extends StatelessWidget {
|
|
|
30
25
|
children: List.generate(book.chapters!.length, (index) {
|
|
31
26
|
return TextButton(
|
|
32
27
|
child: Text("${index + 1}"),
|
|
28
|
+
onPressed: () =>
|
|
33
|
-
|
|
29
|
+
context.dispatch(ReplaceBookChapterAction(context.router, bible.name!, bookIndex, index)),
|
|
34
30
|
);
|
|
35
31
|
}),
|
|
36
32
|
),
|
lib/screens/chapter_view_screen.dart
CHANGED
|
@@ -18,12 +18,6 @@ class ChapterViewScreen extends StatelessWidget {
|
|
|
18
18
|
@override
|
|
19
19
|
Widget build(BuildContext context) {
|
|
20
20
|
final bible = context.select((s) => s.bible);
|
|
21
|
-
if (bible == null) {
|
|
22
|
-
return ColoredBox(
|
|
23
|
-
color: Theme.of(context).colorScheme.surface,
|
|
24
|
-
child: const Center(child: CircularProgressIndicator()),
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
21
|
final book = bible.books![bookIndex];
|
|
28
22
|
final chapter = book.chapters![chapterIndex];
|
|
29
23
|
return Scaffold(
|
lib/sheets/actions_sheet.dart
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import "package:flutter/material.dart";
|
|
2
2
|
import "package:only_bible_app/gen/bible.gen.dart";
|
|
3
|
-
import "package:only_bible_app/store/app_navigator.dart";
|
|
4
3
|
import "package:only_bible_app/store/actions.dart";
|
|
5
4
|
import "package:only_bible_app/theme.dart";
|
|
6
5
|
import "package:only_bible_app/utils.dart";
|
|
@@ -14,7 +13,7 @@ class ActionsSheet extends StatelessWidget {
|
|
|
14
13
|
@override
|
|
15
14
|
Widget build(BuildContext context) {
|
|
16
15
|
final darkMode = context.select((s) => s.darkMode);
|
|
17
|
-
final isPlaying = context.
|
|
16
|
+
final isPlaying = context.isWaiting(TogglePlayAction);
|
|
18
17
|
final iconColor = darkMode ? Colors.white.withOpacity(0.9) : Colors.black.withOpacity(0.9);
|
|
19
18
|
final audioIcon = isPlaying ? Icons.pause_circle_outline : Icons.play_circle_outline;
|
|
20
19
|
|
|
@@ -25,7 +24,7 @@ class ActionsSheet extends StatelessWidget {
|
|
|
25
24
|
index,
|
|
26
25
|
),
|
|
27
26
|
);
|
|
28
|
-
context.
|
|
27
|
+
context.dispatch(HideActionsAction());
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
return Material(
|
|
@@ -46,7 +45,7 @@ class ActionsSheet extends StatelessWidget {
|
|
|
46
45
|
List<Verse>.from(context.read().selectedVerses),
|
|
47
46
|
),
|
|
48
47
|
);
|
|
49
|
-
context.
|
|
48
|
+
context.dispatch(HideActionsAction());
|
|
50
49
|
},
|
|
51
50
|
icon: Icon(Icons.cancel_outlined, size: 28, color: iconColor),
|
|
52
51
|
),
|
|
@@ -73,13 +72,20 @@ class ActionsSheet extends StatelessWidget {
|
|
|
73
72
|
IconButton(
|
|
74
73
|
padding: EdgeInsets.zero,
|
|
75
74
|
onPressed: () {
|
|
76
|
-
context.
|
|
75
|
+
context.dispatch(TogglePlayAction(context, bible));
|
|
77
76
|
},
|
|
78
77
|
icon: Icon(audioIcon, size: 34, color: iconColor),
|
|
79
78
|
),
|
|
80
79
|
IconButton(
|
|
81
80
|
padding: EdgeInsets.zero,
|
|
81
|
+
onPressed: () {
|
|
82
|
-
|
|
82
|
+
final verses = context.read().selectedVerses;
|
|
83
|
+
context.dispatch(ShareVersesAction(
|
|
84
|
+
verses,
|
|
85
|
+
context.bookNames[verses.first.book],
|
|
86
|
+
context.read().languageCode,
|
|
87
|
+
));
|
|
88
|
+
},
|
|
83
89
|
icon: Icon(Icons.share_outlined, size: 34, color: iconColor),
|
|
84
90
|
),
|
|
85
91
|
],
|
lib/sheets/settings_sheet.dart
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "package:flutter/material.dart";
|
|
2
2
|
import "package:only_bible_app/gen/bible.gen.dart";
|
|
3
|
-
import "package:only_bible_app/store/app_navigator.dart";
|
|
4
3
|
import "package:only_bible_app/store/actions.dart";
|
|
4
|
+
import "package:only_bible_app/store/app_navigator.dart";
|
|
5
5
|
import "package:only_bible_app/utils.dart";
|
|
6
6
|
import "package:settings_ui/settings_ui.dart";
|
|
7
7
|
|
|
@@ -36,7 +36,7 @@ class SettingsSheet extends StatelessWidget {
|
|
|
36
36
|
leading: const Icon(Icons.book_outlined, color: Colors.blueAccent),
|
|
37
37
|
title: Text(context.l.bibleTitle),
|
|
38
38
|
value: Text(bible.name!),
|
|
39
|
-
onPressed: context.
|
|
39
|
+
onPressed: (_) => context.dispatch(ChangeBibleAction(context.router)),
|
|
40
40
|
),
|
|
41
41
|
SettingsTile.navigation(
|
|
42
42
|
leading: const Icon(Icons.color_lens_outlined, color: Colors.green),
|
|
@@ -123,7 +123,7 @@ class SettingsSheet extends StatelessWidget {
|
|
|
123
123
|
SettingsTile.navigation(
|
|
124
124
|
leading: const Icon(Icons.policy_outlined, color: Colors.brown),
|
|
125
125
|
title: Text(context.l.privacyPolicyTitle),
|
|
126
|
-
onPressed: context.
|
|
126
|
+
onPressed: (_) => context.dispatch(ShowPrivacyPolicyAction(context.router)),
|
|
127
127
|
),
|
|
128
128
|
SettingsTile.navigation(
|
|
129
129
|
leading: const Icon(
|
|
@@ -131,17 +131,17 @@ class SettingsSheet extends StatelessWidget {
|
|
|
131
131
|
color: Colors.blueGrey,
|
|
132
132
|
),
|
|
133
133
|
title: Text(context.l.termsAndConditionsTitle),
|
|
134
|
-
onPressed: context.
|
|
134
|
+
onPressed: (_) => context.dispatch(ShowTermsAndConditionsAction(context.router)),
|
|
135
135
|
),
|
|
136
136
|
SettingsTile.navigation(
|
|
137
137
|
leading: const Icon(Icons.share_outlined, color: Colors.blueAccent),
|
|
138
138
|
title: Text(context.l.shareAppTitle),
|
|
139
|
-
onPressed: context.
|
|
139
|
+
onPressed: (_) => context.dispatch(ShareAppLinkAction()),
|
|
140
140
|
),
|
|
141
141
|
SettingsTile.navigation(
|
|
142
142
|
leading: Icon(Icons.star, color: Colors.yellowAccent.shade700),
|
|
143
143
|
title: Text(context.l.rateAppTitle),
|
|
144
|
-
onPressed: context.
|
|
144
|
+
onPressed: (_) => context.dispatch(RateAppAction()),
|
|
145
145
|
),
|
|
146
146
|
SettingsTile.navigation(
|
|
147
147
|
leading: Icon(
|
|
@@ -149,7 +149,7 @@ class SettingsSheet extends StatelessWidget {
|
|
|
149
149
|
color: context.theme.colorScheme.onSurface,
|
|
150
150
|
),
|
|
151
151
|
title: Text(context.l.aboutUsTitle),
|
|
152
|
-
onPressed: context.
|
|
152
|
+
onPressed: (_) => context.dispatch(ShowAboutUsAction(context.router)),
|
|
153
153
|
),
|
|
154
154
|
],
|
|
155
155
|
),
|
lib/store/actions.dart
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
|
+
import "dart:developer";
|
|
2
|
+
|
|
3
|
+
import "package:app_review/app_review.dart";
|
|
1
4
|
import "package:async_redux/async_redux.dart";
|
|
5
|
+
import "package:flutter/material.dart";
|
|
6
|
+
import "package:go_router/go_router.dart";
|
|
7
|
+
import "package:only_bible_app/dialog.dart";
|
|
2
8
|
import "package:only_bible_app/gen/bible.gen.dart";
|
|
9
|
+
import "package:only_bible_app/sheets/settings_sheet.dart";
|
|
3
10
|
import "package:only_bible_app/store/app_state.dart";
|
|
11
|
+
import "package:only_bible_app/store/buffer_audio_source.dart";
|
|
4
12
|
import "package:only_bible_app/utils.dart";
|
|
5
|
-
|
|
6
|
-
class LoadBibleAction extends ReduxAction<AppState> {
|
|
7
|
-
@override
|
|
8
|
-
Future<AppState> reduce() async {
|
|
9
|
-
|
|
13
|
+
import "package:only_bible_app/widgets/verses_view.dart";
|
|
10
|
-
return state;
|
|
11
|
-
}
|
|
12
|
-
|
|
14
|
+
import "package:share_plus/share_plus.dart";
|
|
13
|
-
return state.copy(bible: bible);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
15
|
|
|
17
16
|
class FirstOpenDoneAction extends ReduxAction<AppState> {
|
|
18
17
|
@override
|
|
@@ -63,13 +62,45 @@ class UpdateChapterAction extends ReduxAction<AppState> {
|
|
|
63
62
|
AppState reduce() => state.copy(savedBook: book, savedChapter: chapter);
|
|
64
63
|
}
|
|
65
64
|
|
|
66
|
-
class
|
|
65
|
+
class TogglePlayAction extends ReduxAction<AppState> {
|
|
66
|
+
final BuildContext buildContext;
|
|
67
|
-
final
|
|
67
|
+
final Bible bible;
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
TogglePlayAction(this.buildContext, this.bible);
|
|
70
70
|
|
|
71
71
|
@override
|
|
72
|
+
Future<AppState?> reduce() async {
|
|
73
|
+
if (audioPlayer.playing) {
|
|
74
|
+
await audioPlayer.pause();
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
final versesToPlay = List<Verse>.from(state.selectedVerses);
|
|
78
|
+
final audioVoice = buildContext.currentLang.audioVoice;
|
|
79
|
+
final audioError = buildContext.l.audioError;
|
|
80
|
+
for (final v in versesToPlay) {
|
|
81
|
+
final pathname = "${bible.name!}_${v.book}_${v.chapter}_${v.index}";
|
|
82
|
+
try {
|
|
83
|
+
final data = await convertText(audioVoice, v.text ?? "");
|
|
84
|
+
await audioPlayer.setAudioSource(BufferAudioSource(data));
|
|
85
|
+
await audioPlayer.play();
|
|
86
|
+
await audioPlayer.stop();
|
|
87
|
+
} catch (err) {
|
|
88
|
+
log(
|
|
89
|
+
"Could not play audio",
|
|
90
|
+
name: "play",
|
|
91
|
+
error: (err.toString(), pathname),
|
|
92
|
+
);
|
|
72
|
-
|
|
93
|
+
recordError((err.toString(), pathname).toString(), null);
|
|
94
|
+
if (buildContext.mounted) {
|
|
95
|
+
showError(buildContext, audioError);
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
} finally {
|
|
99
|
+
await audioPlayer.pause();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
73
104
|
}
|
|
74
105
|
|
|
75
106
|
class SelectVerseAction extends ReduxAction<AppState> {
|
|
@@ -82,15 +113,12 @@ class SelectVerseAction extends ReduxAction<AppState> {
|
|
|
82
113
|
final isSelected = state.selectedVerses.any(
|
|
83
114
|
(el) => el.book == verse.book && el.chapter == verse.chapter && el.index == verse.index,
|
|
84
115
|
);
|
|
85
|
-
|
|
116
|
+
final newVerses = isSelected
|
|
117
|
+
? state.selectedVerses.where((it) => it.index != verse.index).toList()
|
|
118
|
+
: [...state.selectedVerses, verse];
|
|
86
|
-
|
|
119
|
+
return state.copy(
|
|
87
|
-
|
|
120
|
+
selectedVerses: newVerses,
|
|
88
|
-
|
|
121
|
+
);
|
|
89
|
-
} else {
|
|
90
|
-
return state.copy(
|
|
91
|
-
selectedVerses: [...state.selectedVerses, verse],
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
122
|
}
|
|
95
123
|
}
|
|
96
124
|
|
|
@@ -129,3 +157,302 @@ class RemoveHighlightAction extends ReduxAction<AppState> {
|
|
|
129
157
|
return state.copy(highlights: updated);
|
|
130
158
|
}
|
|
131
159
|
}
|
|
160
|
+
|
|
161
|
+
// ─── Navigation actions ──────────────────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
class HideActionsAction extends ReduxAction<AppState> {
|
|
164
|
+
@override
|
|
165
|
+
AppState? reduce() {
|
|
166
|
+
return state.selectedVerses.isEmpty ? null : state.copy(selectedVerses: []);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
class ShowSettingsAction extends ReduxAction<AppState> {
|
|
171
|
+
final BuildContext buildContext;
|
|
172
|
+
final Bible bible;
|
|
173
|
+
|
|
174
|
+
ShowSettingsAction(this.buildContext, this.bible);
|
|
175
|
+
|
|
176
|
+
@override
|
|
177
|
+
AppState? reduce() {
|
|
178
|
+
showModalBottomSheet(
|
|
179
|
+
context: buildContext,
|
|
180
|
+
isDismissible: true,
|
|
181
|
+
enableDrag: true,
|
|
182
|
+
showDragHandle: true,
|
|
183
|
+
useSafeArea: true,
|
|
184
|
+
builder: (context) => SettingsSheet(bible: bible),
|
|
185
|
+
);
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
class PushBookChapterAction extends ReduxAction<AppState> {
|
|
191
|
+
final GoRouter router;
|
|
192
|
+
final String bibleName;
|
|
193
|
+
final int book;
|
|
194
|
+
final int chapter;
|
|
195
|
+
final TextDirection? dir;
|
|
196
|
+
|
|
197
|
+
PushBookChapterAction(this.router, this.bibleName, this.book, this.chapter, [this.dir]);
|
|
198
|
+
|
|
199
|
+
@override
|
|
200
|
+
AppState reduce() {
|
|
201
|
+
audioPlayer.pause();
|
|
202
|
+
router.push(
|
|
203
|
+
"/chapter/${Uri.encodeComponent(bibleName)}/$book/$chapter",
|
|
204
|
+
extra: dir,
|
|
205
|
+
);
|
|
206
|
+
return state.copy(
|
|
207
|
+
savedBook: book,
|
|
208
|
+
savedChapter: chapter,
|
|
209
|
+
selectedVerses: [],
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
class ReplaceBookChapterAction extends ReduxAction<AppState> {
|
|
215
|
+
final GoRouter router;
|
|
216
|
+
final String bibleName;
|
|
217
|
+
final int book;
|
|
218
|
+
final int chapter;
|
|
219
|
+
|
|
220
|
+
ReplaceBookChapterAction(this.router, this.bibleName, this.book, this.chapter);
|
|
221
|
+
|
|
222
|
+
@override
|
|
223
|
+
AppState reduce() {
|
|
224
|
+
audioPlayer.pause();
|
|
225
|
+
router.go("/chapter/${Uri.encodeComponent(bibleName)}/$book/$chapter");
|
|
226
|
+
return state.copy(
|
|
227
|
+
savedBook: book,
|
|
228
|
+
savedChapter: chapter,
|
|
229
|
+
selectedVerses: [],
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
class NextChapterAction extends ReduxAction<AppState> {
|
|
235
|
+
final GoRouter router;
|
|
236
|
+
final Bible bible;
|
|
237
|
+
final int book;
|
|
238
|
+
final int chapter;
|
|
239
|
+
|
|
240
|
+
NextChapterAction(this.router, this.bible, this.book, this.chapter);
|
|
241
|
+
|
|
242
|
+
@override
|
|
243
|
+
AppState? reduce() {
|
|
244
|
+
final selectedBook = bible.books![book];
|
|
245
|
+
int? newBook;
|
|
246
|
+
int? newChapter;
|
|
247
|
+
|
|
248
|
+
if (selectedBook.chapters!.length > chapter + 1) {
|
|
249
|
+
newBook = selectedBook.index;
|
|
250
|
+
newChapter = chapter + 1;
|
|
251
|
+
} else if (selectedBook.index + 1 < bible.books!.length) {
|
|
252
|
+
final nextBook = bible.books![selectedBook.index + 1];
|
|
253
|
+
newBook = nextBook.index;
|
|
254
|
+
newChapter = 0;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (newBook == null) return null;
|
|
258
|
+
audioPlayer.pause();
|
|
259
|
+
router.push(
|
|
260
|
+
"/chapter/${Uri.encodeComponent(bible.name!)}/$newBook/$newChapter",
|
|
261
|
+
extra: TextDirection.ltr,
|
|
262
|
+
);
|
|
263
|
+
return state.copy(
|
|
264
|
+
savedBook: newBook,
|
|
265
|
+
savedChapter: newChapter,
|
|
266
|
+
selectedVerses: [],
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
class PreviousChapterAction extends ReduxAction<AppState> {
|
|
272
|
+
final GoRouter router;
|
|
273
|
+
final Bible bible;
|
|
274
|
+
final int book;
|
|
275
|
+
final int chapter;
|
|
276
|
+
|
|
277
|
+
PreviousChapterAction(this.router, this.bible, this.book, this.chapter);
|
|
278
|
+
|
|
279
|
+
@override
|
|
280
|
+
AppState? reduce() {
|
|
281
|
+
final selectedBook = bible.books![book];
|
|
282
|
+
int? newBook;
|
|
283
|
+
int? newChapter;
|
|
284
|
+
|
|
285
|
+
if (chapter - 1 >= 0) {
|
|
286
|
+
newBook = selectedBook.index;
|
|
287
|
+
newChapter = chapter - 1;
|
|
288
|
+
} else if (selectedBook.index - 1 >= 0) {
|
|
289
|
+
final prevBook = bible.books![selectedBook.index - 1];
|
|
290
|
+
newBook = prevBook.index;
|
|
291
|
+
newChapter = prevBook.chapters!.length - 1;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (newBook == null) return null;
|
|
295
|
+
audioPlayer.pause();
|
|
296
|
+
router.push(
|
|
297
|
+
"/chapter/${Uri.encodeComponent(bible.name!)}/$newBook/$newChapter",
|
|
298
|
+
extra: TextDirection.rtl,
|
|
299
|
+
);
|
|
300
|
+
return state.copy(
|
|
301
|
+
savedBook: newBook,
|
|
302
|
+
savedChapter: newChapter,
|
|
303
|
+
selectedVerses: [],
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
class ChangeBibleAction extends ReduxAction<AppState> {
|
|
309
|
+
final GoRouter router;
|
|
310
|
+
|
|
311
|
+
ChangeBibleAction(this.router);
|
|
312
|
+
|
|
313
|
+
@override
|
|
314
|
+
AppState? reduce() {
|
|
315
|
+
router.go("/bible");
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
class PushChangeBibleAction extends ReduxAction<AppState> {
|
|
321
|
+
final GoRouter router;
|
|
322
|
+
|
|
323
|
+
PushChangeBibleAction(this.router);
|
|
324
|
+
|
|
325
|
+
@override
|
|
326
|
+
AppState? reduce() {
|
|
327
|
+
router.push("/bible");
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
class ChangeBookAction extends ReduxAction<AppState> {
|
|
333
|
+
final GoRouter router;
|
|
334
|
+
final Bible bible;
|
|
335
|
+
|
|
336
|
+
ChangeBookAction(this.router, this.bible);
|
|
337
|
+
|
|
338
|
+
@override
|
|
339
|
+
AppState? reduce() {
|
|
340
|
+
router.push("/books/${Uri.encodeComponent(bible.name!)}");
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
class ChangeChapterAction extends ReduxAction<AppState> {
|
|
346
|
+
final GoRouter router;
|
|
347
|
+
final Bible bible;
|
|
348
|
+
final Book book;
|
|
349
|
+
|
|
350
|
+
ChangeChapterAction(this.router, this.bible, this.book);
|
|
351
|
+
|
|
352
|
+
@override
|
|
353
|
+
AppState? reduce() {
|
|
354
|
+
router.push("/chapters/${Uri.encodeComponent(bible.name!)}/${book.index}");
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
class UpdateCurrentBibleAction extends ReduxAction<AppState> {
|
|
360
|
+
final GoRouter router;
|
|
361
|
+
final String name;
|
|
362
|
+
final String code;
|
|
363
|
+
final int book;
|
|
364
|
+
final int chapter;
|
|
365
|
+
|
|
366
|
+
UpdateCurrentBibleAction(this.router, this.name, this.code, this.book, this.chapter);
|
|
367
|
+
|
|
368
|
+
@override
|
|
369
|
+
Future<AppState> reduce() async {
|
|
370
|
+
final bible = await loadBible(name);
|
|
371
|
+
router.go("/chapter/${Uri.encodeComponent(name)}/$book/$chapter");
|
|
372
|
+
return state.copy(
|
|
373
|
+
bibleName: name,
|
|
374
|
+
languageCode: code,
|
|
375
|
+
bible: bible,
|
|
376
|
+
savedBook: book,
|
|
377
|
+
savedChapter: chapter,
|
|
378
|
+
selectedVerses: [],
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
class ShowAboutUsAction extends ReduxAction<AppState> {
|
|
384
|
+
final GoRouter router;
|
|
385
|
+
|
|
386
|
+
ShowAboutUsAction(this.router);
|
|
387
|
+
|
|
388
|
+
@override
|
|
389
|
+
AppState? reduce() {
|
|
390
|
+
router.push("/webview", extra: "https://onlybible.app/about-us");
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
class ShowPrivacyPolicyAction extends ReduxAction<AppState> {
|
|
396
|
+
final GoRouter router;
|
|
397
|
+
|
|
398
|
+
ShowPrivacyPolicyAction(this.router);
|
|
399
|
+
|
|
400
|
+
@override
|
|
401
|
+
AppState? reduce() {
|
|
402
|
+
router.push("/webview", extra: "https://onlybible.app/privacy-policy");
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
class ShowTermsAndConditionsAction extends ReduxAction<AppState> {
|
|
408
|
+
final GoRouter router;
|
|
409
|
+
|
|
410
|
+
ShowTermsAndConditionsAction(this.router);
|
|
411
|
+
|
|
412
|
+
@override
|
|
413
|
+
AppState? reduce() {
|
|
414
|
+
router.push("/webview", extra: "https://onlybible.app/terms-and-conditions");
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
class ShareAppLinkAction extends ReduxAction<AppState> {
|
|
420
|
+
@override
|
|
421
|
+
AppState? reduce() {
|
|
422
|
+
final url = isAndroid()
|
|
423
|
+
? "https://play.google.com/store/apps/details?id=sh.pyros.only_bible_app"
|
|
424
|
+
: "https://apps.apple.com/us/app/only-bible-app/id6467606465";
|
|
425
|
+
SharePlus.instance.share(ShareParams(subject: "Only Bible App", text: url));
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
class RateAppAction extends ReduxAction<AppState> {
|
|
431
|
+
@override
|
|
432
|
+
AppState? reduce() {
|
|
433
|
+
AppReview.requestReview;
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
class ShareVersesAction extends ReduxAction<AppState> {
|
|
439
|
+
final List<Verse> verses;
|
|
440
|
+
final String bookName;
|
|
441
|
+
final String languageCode;
|
|
442
|
+
|
|
443
|
+
ShareVersesAction(this.verses, this.bookName, this.languageCode);
|
|
444
|
+
|
|
445
|
+
@override
|
|
446
|
+
Future<AppState?> reduce() async {
|
|
447
|
+
final chapter = verses.first.chapter + 1;
|
|
448
|
+
final items = verses.sortedBy((e) => e.index).map((e) => e.index + 1);
|
|
449
|
+
final versesThrough = items.length >= 3 ? "${items.first}-${items.last}" : items.join(",");
|
|
450
|
+
final version = languageCode == "en" ? "KJV" : "";
|
|
451
|
+
final title = "$bookName $chapter:$versesThrough $version";
|
|
452
|
+
final text = verses.map((e) => e.text ?? "").join("\n");
|
|
453
|
+
await SharePlus.instance.share(
|
|
454
|
+
ShareParams(title: title, subject: title, text: "$title\n$text"),
|
|
455
|
+
);
|
|
456
|
+
return state.copy(selectedVerses: []);
|
|
457
|
+
}
|
|
458
|
+
}
|
lib/store/app_navigator.dart
CHANGED
|
@@ -1,260 +1,21 @@
|
|
|
1
|
-
import "package:async_redux/async_redux.dart" show Store, StoreProvider;
|
|
2
1
|
import "package:flutter/material.dart";
|
|
3
|
-
import "package:app_review/app_review.dart";
|
|
4
2
|
import "package:go_router/go_router.dart";
|
|
5
|
-
import "package:only_bible_app/gen/bible.gen.dart";
|
|
6
|
-
import "package:only_bible_app/sheets/actions_sheet.dart";
|
|
7
|
-
import "package:only_bible_app/sheets/settings_sheet.dart";
|
|
8
|
-
import "package:only_bible_app/store/actions.dart";
|
|
9
|
-
import "package:only_bible_app/store/app_state.dart";
|
|
10
|
-
import "package:only_bible_app/utils.dart";
|
|
11
|
-
import "package:share_plus/share_plus.dart";
|
|
12
3
|
|
|
13
|
-
class
|
|
4
|
+
class AppRouterScope extends InheritedWidget {
|
|
14
|
-
final Store<AppState> store;
|
|
15
|
-
final AppNavigator navigator;
|
|
16
|
-
final
|
|
5
|
+
final GoRouter router;
|
|
17
6
|
|
|
18
|
-
const
|
|
7
|
+
const AppRouterScope({
|
|
19
8
|
super.key,
|
|
20
|
-
required this.
|
|
9
|
+
required this.router,
|
|
21
|
-
required this.navigator,
|
|
22
|
-
required this.child,
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
static AppNavigator of(BuildContext context) {
|
|
26
|
-
return context.dependOnInheritedWidgetOfExactType<_InheritedAppNavigator>()!.navigator;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
@override
|
|
30
|
-
Widget build(BuildContext context) {
|
|
31
|
-
return StoreProvider<AppState>(
|
|
32
|
-
store: store,
|
|
33
|
-
child: _InheritedAppNavigator(
|
|
34
|
-
navigator: navigator,
|
|
35
|
-
child: child,
|
|
36
|
-
),
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
class _InheritedAppNavigator extends InheritedWidget {
|
|
42
|
-
final AppNavigator navigator;
|
|
43
|
-
|
|
44
|
-
const _InheritedAppNavigator({
|
|
45
|
-
required this.navigator,
|
|
46
10
|
required super.child,
|
|
47
11
|
});
|
|
48
12
|
|
|
49
|
-
@override
|
|
50
|
-
|
|
13
|
+
static AppRouterScope of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<AppRouterScope>()!;
|
|
51
|
-
}
|
|
52
14
|
|
|
53
|
-
|
|
15
|
+
@override
|
|
54
|
-
|
|
16
|
+
bool updateShouldNotify(AppRouterScope old) => false;
|
|
55
17
|
}
|
|
56
18
|
|
|
57
|
-
class AppNavigator {
|
|
58
|
-
final Store<AppState> store;
|
|
59
|
-
OverlayEntry? _actionsOverlay;
|
|
60
|
-
|
|
61
|
-
AppNavigator(this.store);
|
|
62
|
-
|
|
63
|
-
String _chapterPath(String bibleName, int book, int chapter) {
|
|
64
|
-
return "/chapter/${Uri.encodeComponent(bibleName)}/$book/$chapter";
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
void pushBookChapter(
|
|
68
|
-
BuildContext context,
|
|
69
|
-
String bibleName,
|
|
70
|
-
int book,
|
|
71
|
-
int chapter,
|
|
72
|
-
TextDirection? dir,
|
|
73
|
-
) {
|
|
74
|
-
store.dispatch(UpdateChapterAction(book, chapter));
|
|
75
|
-
if (store.state.isPlaying) {
|
|
76
|
-
AppState.player.pause();
|
|
77
|
-
store.dispatch(SetPlayingAction(false));
|
|
78
|
-
}
|
|
79
|
-
hideActions();
|
|
80
|
-
context.push(_chapterPath(bibleName, book, chapter), extra: dir);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
void replaceBookChapter(
|
|
84
|
-
BuildContext context,
|
|
85
|
-
String bibleName,
|
|
86
|
-
int book,
|
|
87
|
-
int chapter,
|
|
88
|
-
) {
|
|
89
|
-
store.dispatch(UpdateChapterAction(book, chapter));
|
|
90
|
-
if (store.state.isPlaying) {
|
|
91
|
-
AppState.player.pause();
|
|
92
|
-
store.dispatch(SetPlayingAction(false));
|
|
93
|
-
}
|
|
94
|
-
hideActions();
|
|
95
|
-
context.go(_chapterPath(bibleName, book, chapter));
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
void nextChapter(BuildContext context, Bible bible, int book, int chapter) {
|
|
99
|
-
final selectedBook = bible.books![book];
|
|
100
|
-
if (selectedBook.chapters!.length > chapter + 1) {
|
|
101
|
-
pushBookChapter(
|
|
102
|
-
context,
|
|
103
|
-
bible.name!,
|
|
104
|
-
selectedBook.index,
|
|
105
|
-
chapter + 1,
|
|
106
|
-
TextDirection.ltr,
|
|
107
|
-
);
|
|
108
|
-
} else {
|
|
109
|
-
if (selectedBook.index + 1 < bible.books!.length) {
|
|
110
|
-
final nextBook = bible.books![selectedBook.index + 1];
|
|
111
|
-
pushBookChapter(
|
|
112
|
-
context,
|
|
113
|
-
bible.name!,
|
|
114
|
-
nextBook.index,
|
|
115
|
-
0,
|
|
116
|
-
TextDirection.ltr,
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
void previousChapter(BuildContext context, Bible bible, int book, int chapter) {
|
|
123
|
-
final selectedBook = bible.books![book];
|
|
124
|
-
if (chapter - 1 >= 0) {
|
|
125
|
-
pushBookChapter(
|
|
126
|
-
context,
|
|
127
|
-
bible.name!,
|
|
128
|
-
selectedBook.index,
|
|
129
|
-
chapter - 1,
|
|
130
|
-
TextDirection.rtl,
|
|
131
|
-
);
|
|
132
|
-
} else {
|
|
133
|
-
if (selectedBook.index - 1 >= 0) {
|
|
134
|
-
final prevBook = bible.books![selectedBook.index - 1];
|
|
135
|
-
pushBookChapter(
|
|
136
|
-
context,
|
|
137
|
-
bible.name!,
|
|
138
|
-
prevBook.index,
|
|
139
|
-
prevBook.chapters!.length - 1,
|
|
140
|
-
TextDirection.rtl,
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
void showAboutUs(BuildContext context) {
|
|
147
|
-
context.push("/webview", extra: "https://onlybible.app/about-us");
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
void showPrivacyPolicy(BuildContext context) {
|
|
151
|
-
context.push("/webview", extra: "https://onlybible.app/privacy-policy");
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
void showTermsAndConditions(BuildContext context) {
|
|
155
|
-
context.push("/webview", extra: "https://onlybible.app/terms-and-conditions");
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
void changeBible(BuildContext context) {
|
|
159
|
-
context.go("/bible");
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
void changeBibleFromHeader(BuildContext context) {
|
|
163
|
-
context.push("/bible");
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
void changeBook(BuildContext context, Bible bible) {
|
|
167
|
-
context.push("/books/${Uri.encodeComponent(bible.name!)}");
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
void changeChapter(BuildContext context, Bible bible, Book book, int index) {
|
|
171
|
-
context.push("/chapters/${Uri.encodeComponent(bible.name!)}/${book.index}");
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
Future<void> updateCurrentBible(
|
|
175
|
-
BuildContext context,
|
|
176
|
-
String name,
|
|
177
|
-
String code,
|
|
178
|
-
int book,
|
|
179
|
-
int chapter,
|
|
180
|
-
) async {
|
|
181
|
-
hideActions();
|
|
182
|
-
await store.dispatchAndWait(UpdateBibleAction(name, code));
|
|
183
|
-
await store.dispatchAndWait(LoadBibleAction());
|
|
184
|
-
context.go(_chapterPath(name, book, chapter));
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
19
|
+
extension AppRouterContext on BuildContext {
|
|
188
|
-
if (isAndroid()) {
|
|
189
|
-
Share.share(
|
|
190
|
-
subject: "Only Bible App",
|
|
191
|
-
"https://play.google.com/store/apps/details?id=sh.pyros.only_bible_app",
|
|
192
|
-
);
|
|
193
|
-
} else {
|
|
194
|
-
Share.share(
|
|
195
|
-
subject: "Only Bible App",
|
|
196
|
-
"https://apps.apple.com/us/app/only-bible-app/id6467606465",
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
Future<void> rateApp(BuildContext context) async {
|
|
202
|
-
AppReview.requestReview;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
Future<void> shareVerses(BuildContext context, Bible bible, List<Verse> verses) async {
|
|
206
|
-
final name = context.bookNames[verses.first.book];
|
|
207
|
-
final chapter = verses.first.chapter + 1;
|
|
208
|
-
final items = verses.sortedBy((e) => e.index).map((e) => e.index + 1);
|
|
209
|
-
final versesThrough = items.length >= 3 ? "${items.first}-${items.last}" : items.join(",");
|
|
210
|
-
final version = context.currentLang.languageCode == "en" ? "KJV" : "";
|
|
211
|
-
final title = "$name $chapter:$versesThrough $version";
|
|
212
|
-
final text = verses.map((e) => e.text ?? "").join("\n");
|
|
213
|
-
await SharePlus.instance.share(
|
|
214
|
-
ShareParams(
|
|
215
|
-
title: title,
|
|
216
|
-
subject: title,
|
|
217
|
-
text: "$title\n$text",
|
|
218
|
-
),
|
|
219
|
-
);
|
|
220
|
-
hideActions();
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
void showSettings(BuildContext context, Bible bible) {
|
|
224
|
-
showModalBottomSheet(
|
|
225
|
-
context: context,
|
|
226
|
-
isDismissible: true,
|
|
227
|
-
enableDrag: true,
|
|
228
|
-
showDragHandle: true,
|
|
229
|
-
useSafeArea: true,
|
|
230
|
-
|
|
20
|
+
GoRouter get router => AppRouterScope.of(this).router;
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
void showActions(BuildContext context, Bible bible) {
|
|
235
|
-
_actionsOverlay?.remove();
|
|
236
|
-
_actionsOverlay = null;
|
|
237
|
-
final overlay = Overlay.of(context);
|
|
238
|
-
_actionsOverlay = OverlayEntry(
|
|
239
|
-
builder: (context) {
|
|
240
|
-
final bottomPadding = MediaQuery.of(context).padding.bottom;
|
|
241
|
-
return Align(
|
|
242
|
-
alignment: Alignment.bottomCenter,
|
|
243
|
-
child: Padding(
|
|
244
|
-
padding: EdgeInsets.only(bottom: bottomPadding + 40, left: 20, right: 20),
|
|
245
|
-
child: ActionsSheet(bible: bible),
|
|
246
|
-
),
|
|
247
|
-
);
|
|
248
|
-
},
|
|
249
|
-
);
|
|
250
|
-
overlay.insert(_actionsOverlay!);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
void hideActions() {
|
|
254
|
-
if (_actionsOverlay != null) {
|
|
255
|
-
_actionsOverlay?.remove();
|
|
256
|
-
_actionsOverlay = null;
|
|
257
|
-
store.dispatch(ClearSelectedVersesAction());
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
21
|
}
|
lib/store/app_persistor.dart
CHANGED
|
@@ -15,12 +15,15 @@ class AppPersistor extends Persistor<AppState> {
|
|
|
15
15
|
|
|
16
16
|
@override
|
|
17
17
|
Future<AppState?> readState() async {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
Future<Map<String, dynamic>?> readJson() async {
|
|
18
22
|
try {
|
|
19
23
|
final file = await _file;
|
|
20
24
|
if (await file.exists()) {
|
|
21
25
|
final contents = await file.readAsString();
|
|
22
|
-
|
|
26
|
+
return jsonDecode(contents) as Map<String, dynamic>;
|
|
23
|
-
return AppState.fromJson(json);
|
|
24
27
|
}
|
|
25
28
|
} catch (_) {}
|
|
26
29
|
return null;
|
lib/store/app_state.dart
CHANGED
|
@@ -1,17 +1,8 @@
|
|
|
1
|
-
import "dart:developer";
|
|
2
1
|
import "package:flutter/material.dart";
|
|
3
|
-
import "package:just_audio/just_audio.dart";
|
|
4
|
-
import "package:only_bible_app/dialog.dart";
|
|
5
2
|
import "package:only_bible_app/gen/bible.gen.dart";
|
|
6
|
-
import "package:only_bible_app/store/actions.dart";
|
|
7
|
-
import "package:only_bible_app/store/buffer_audio_source.dart";
|
|
8
3
|
import "package:only_bible_app/theme.dart";
|
|
9
|
-
import "package:only_bible_app/utils.dart";
|
|
10
4
|
|
|
11
5
|
class AppState {
|
|
12
|
-
static final player = AudioPlayer();
|
|
13
|
-
static final noteTextController = TextEditingController();
|
|
14
|
-
|
|
15
6
|
final bool firstOpen;
|
|
16
7
|
final String languageCode;
|
|
17
8
|
final String bibleName;
|
|
@@ -21,12 +12,11 @@ class AppState {
|
|
|
21
12
|
final double textScale;
|
|
22
13
|
final int savedBook;
|
|
23
14
|
final int savedChapter;
|
|
24
|
-
final bool isPlaying;
|
|
25
15
|
final List<Verse> selectedVerses;
|
|
26
16
|
final Map<String, int> highlights;
|
|
27
|
-
final Bible
|
|
17
|
+
final Bible bible;
|
|
28
18
|
|
|
29
|
-
|
|
19
|
+
AppState({
|
|
30
20
|
this.firstOpen = true,
|
|
31
21
|
this.languageCode = "en",
|
|
32
22
|
this.bibleName = "English",
|
|
@@ -36,10 +26,9 @@ class AppState {
|
|
|
36
26
|
this.textScale = 0.0,
|
|
37
27
|
this.savedBook = 0,
|
|
38
28
|
this.savedChapter = 0,
|
|
39
|
-
this.isPlaying = false,
|
|
40
29
|
this.selectedVerses = const [],
|
|
41
30
|
this.highlights = const {},
|
|
42
|
-
this.bible,
|
|
31
|
+
required this.bible,
|
|
43
32
|
});
|
|
44
33
|
|
|
45
34
|
AppState copy({
|
|
@@ -52,7 +41,6 @@ class AppState {
|
|
|
52
41
|
double? textScale,
|
|
53
42
|
int? savedBook,
|
|
54
43
|
int? savedChapter,
|
|
55
|
-
bool? isPlaying,
|
|
56
44
|
List<Verse>? selectedVerses,
|
|
57
45
|
Map<String, int>? highlights,
|
|
58
46
|
Bible? bible,
|
|
@@ -67,7 +55,6 @@ class AppState {
|
|
|
67
55
|
textScale: textScale ?? this.textScale,
|
|
68
56
|
savedBook: savedBook ?? this.savedBook,
|
|
69
57
|
savedChapter: savedChapter ?? this.savedChapter,
|
|
70
|
-
isPlaying: isPlaying ?? this.isPlaying,
|
|
71
58
|
selectedVerses: selectedVerses ?? this.selectedVerses,
|
|
72
59
|
highlights: highlights ?? this.highlights,
|
|
73
60
|
bible: bible ?? this.bible,
|
|
@@ -87,7 +74,7 @@ class AppState {
|
|
|
87
74
|
"highlights": highlights,
|
|
88
75
|
};
|
|
89
76
|
|
|
90
|
-
factory AppState.fromJson(Map<String, dynamic> json) => AppState(
|
|
77
|
+
factory AppState.fromJson(Map<String, dynamic> json, Bible bible) => AppState(
|
|
91
78
|
firstOpen: json["firstOpen"] as bool? ?? true,
|
|
92
79
|
languageCode: json["languageCode"] as String? ?? "en",
|
|
93
80
|
bibleName: json["bibleName"] as String? ?? "English",
|
|
@@ -98,6 +85,7 @@ class AppState {
|
|
|
98
85
|
savedBook: json["savedBook"] as int? ?? 0,
|
|
99
86
|
savedChapter: json["savedChapter"] as int? ?? 0,
|
|
100
87
|
highlights: (json["highlights"] as Map<String, dynamic>?)?.map((k, v) => MapEntry(k, v as int)) ?? const {},
|
|
88
|
+
bible: bible,
|
|
101
89
|
);
|
|
102
90
|
|
|
103
91
|
Color? getHighlight(Verse v) {
|
|
@@ -134,37 +122,4 @@ class AppState {
|
|
|
134
122
|
backgroundColor: highlight ?? Theme.of(context).colorScheme.surface,
|
|
135
123
|
);
|
|
136
124
|
}
|
|
137
|
-
|
|
138
|
-
Future<void> onPlay(BuildContext context, Bible bible) async {
|
|
139
|
-
final versesToPlay = List<Verse>.from(context.read().selectedVerses);
|
|
140
|
-
if (context.read().isPlaying) {
|
|
141
|
-
await player.pause();
|
|
142
|
-
context.dispatch(SetPlayingAction(false));
|
|
143
|
-
} else {
|
|
144
|
-
context.dispatch(SetPlayingAction(true));
|
|
145
|
-
for (final v in versesToPlay) {
|
|
146
|
-
final pathname = "${bible.name!}_${v.book}_${v.chapter}_${v.index}";
|
|
147
|
-
try {
|
|
148
|
-
final data = await convertText(context.currentLang.audioVoice, v.text ?? "");
|
|
149
|
-
await player.setAudioSource(BufferAudioSource(data));
|
|
150
|
-
await player.play();
|
|
151
|
-
await player.stop();
|
|
152
|
-
} catch (err) {
|
|
153
|
-
log(
|
|
154
|
-
"Could not play audio",
|
|
155
|
-
name: "play",
|
|
156
|
-
error: (err.toString(), pathname),
|
|
157
|
-
);
|
|
158
|
-
recordError((err.toString(), pathname).toString(), null);
|
|
159
|
-
if (context.mounted) {
|
|
160
|
-
showError(context, context.l.audioError);
|
|
161
|
-
}
|
|
162
|
-
return;
|
|
163
|
-
} finally {
|
|
164
|
-
await player.pause();
|
|
165
|
-
context.dispatch(SetPlayingAction(false));
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
125
|
}
|
lib/utils.dart
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import "dart:convert";
|
|
2
2
|
import "dart:io";
|
|
3
3
|
import "package:async_redux/async_redux.dart";
|
|
4
|
+
export "package:async_redux/async_redux.dart" show BuildContextExtensionForProviderAndConnector;
|
|
4
5
|
import "package:http/http.dart" as http;
|
|
5
6
|
import "package:flutter/services.dart";
|
|
6
7
|
import "package:package_info_plus/package_info_plus.dart";
|
|
@@ -45,9 +46,6 @@ extension AppStateExtension on BuildContext {
|
|
|
45
46
|
AppState get state => getState<AppState>();
|
|
46
47
|
AppState read() => getRead<AppState>();
|
|
47
48
|
R select<R>(R Function(AppState state) selector) => getSelect<AppState, R>(selector);
|
|
48
|
-
R? event<R>(Evt<R> Function(AppState state) selector) => getEvent<AppState, R>(selector);
|
|
49
|
-
void dispatch(ReduxAction<AppState> action) => StoreProvider.dispatch<AppState>(this, action);
|
|
50
|
-
Future<void> dispatchAndWait(ReduxAction<AppState> action) => StoreProvider.dispatchAndWait<AppState>(this, action);
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
extension AppContext on BuildContext {
|
lib/widgets/chapter_app_bar.dart
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import "package:flutter/material.dart";
|
|
2
2
|
import "package:only_bible_app/gen/bible.gen.dart";
|
|
3
|
+
import "package:only_bible_app/store/actions.dart";
|
|
3
4
|
import "package:only_bible_app/store/app_navigator.dart";
|
|
4
5
|
import "package:only_bible_app/utils.dart";
|
|
5
6
|
|
|
@@ -30,7 +31,7 @@ class ChapterAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|
|
30
31
|
children: [
|
|
31
32
|
InkWell(
|
|
32
33
|
enableFeedback: true,
|
|
33
|
-
onTap: () => context.
|
|
34
|
+
onTap: () => context.dispatch(ChangeBookAction(context.router, bible)),
|
|
34
35
|
child: Text(
|
|
35
36
|
bookName,
|
|
36
37
|
style: Theme.of(context).textTheme.headlineMedium,
|
|
@@ -39,7 +40,7 @@ class ChapterAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|
|
39
40
|
),
|
|
40
41
|
InkWell(
|
|
41
42
|
enableFeedback: true,
|
|
42
|
-
onTap: () => context.
|
|
43
|
+
onTap: () => context.dispatch(ChangeChapterAction(context.router, bible, book)),
|
|
43
44
|
child: Padding(
|
|
44
45
|
padding: const EdgeInsets.only(left: 16),
|
|
45
46
|
child: Text(
|
|
@@ -58,7 +59,7 @@ class ChapterAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|
|
58
59
|
child: IconButton(
|
|
59
60
|
padding: EdgeInsets.zero,
|
|
60
61
|
icon: const Icon(Icons.more_vert, size: 28),
|
|
61
|
-
onPressed: () => context.
|
|
62
|
+
onPressed: () => context.dispatch(ShowSettingsAction(context, bible)),
|
|
62
63
|
),
|
|
63
64
|
),
|
|
64
65
|
],
|
lib/widgets/verses_view.dart
CHANGED
|
@@ -1,25 +1,20 @@
|
|
|
1
1
|
import "package:flutter/material.dart";
|
|
2
2
|
import "package:flutter_swipe_detector/flutter_swipe_detector.dart";
|
|
3
|
+
import "package:just_audio/just_audio.dart";
|
|
3
4
|
import "package:only_bible_app/gen/bible.gen.dart";
|
|
4
|
-
import "package:only_bible_app/
|
|
5
|
+
import "package:only_bible_app/sheets/actions_sheet.dart";
|
|
5
6
|
import "package:only_bible_app/store/actions.dart";
|
|
7
|
+
import "package:only_bible_app/store/app_navigator.dart";
|
|
6
8
|
import "package:only_bible_app/utils.dart";
|
|
7
9
|
|
|
10
|
+
final audioPlayer = AudioPlayer();
|
|
11
|
+
|
|
8
12
|
class VersesView extends StatelessWidget {
|
|
9
13
|
final Bible bible;
|
|
10
14
|
final Chapter chapter;
|
|
11
15
|
|
|
12
16
|
const VersesView({super.key, required this.bible, required this.chapter});
|
|
13
17
|
|
|
14
|
-
void _onVerseTap(BuildContext context, Verse v) {
|
|
15
|
-
context.dispatch(SelectVerseAction(v));
|
|
16
|
-
if (context.read().selectedVerses.isNotEmpty) {
|
|
17
|
-
context.nav.showActions(context, bible);
|
|
18
|
-
} else {
|
|
19
|
-
context.nav.hideActions();
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
18
|
@override
|
|
24
19
|
Widget build(BuildContext context) {
|
|
25
20
|
final (boldFont, textScale, selectedVerses, _, _) =
|
|
@@ -28,45 +23,58 @@ class VersesView extends StatelessWidget {
|
|
|
28
23
|
final textStyle = DefaultTextStyle.of(context).style;
|
|
29
24
|
final theme = Theme.of(context).textTheme;
|
|
30
25
|
final baseStyle = boldFont ? textStyle.copyWith(fontWeight: FontWeight.w500) : textStyle;
|
|
26
|
+
return Stack(
|
|
27
|
+
children: [
|
|
31
|
-
|
|
28
|
+
SwipeDetector(
|
|
29
|
+
onSwipeLeft: (offset) =>
|
|
32
|
-
|
|
30
|
+
context.dispatch(NextChapterAction(context.router, bible, chapter.book, chapter.index)),
|
|
31
|
+
onSwipeRight: (offset) =>
|
|
33
|
-
|
|
32
|
+
context.dispatch(PreviousChapterAction(context.router, bible, chapter.book, chapter.index)),
|
|
34
|
-
|
|
33
|
+
child: SingleChildScrollView(
|
|
35
|
-
|
|
34
|
+
physics: const BouncingScrollPhysics(),
|
|
36
|
-
|
|
35
|
+
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
37
|
-
|
|
36
|
+
child: Column(
|
|
38
|
-
|
|
37
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
39
|
-
|
|
38
|
+
children: [
|
|
40
|
-
|
|
39
|
+
for (final v in chapter.verses!)
|
|
41
|
-
|
|
40
|
+
Padding(
|
|
42
|
-
|
|
41
|
+
padding: const EdgeInsets.only(bottom: 4),
|
|
43
|
-
|
|
42
|
+
child: GestureDetector(
|
|
44
|
-
|
|
43
|
+
onTap: () => context.dispatch(SelectVerseAction(v)),
|
|
45
|
-
|
|
44
|
+
behavior: HitTestBehavior.opaque,
|
|
46
|
-
|
|
45
|
+
child: Text.rich(
|
|
47
|
-
TextSpan(
|
|
48
|
-
style: baseStyle,
|
|
49
|
-
children: [
|
|
50
|
-
if (v.heading != null && v.heading!.isNotEmpty)
|
|
51
|
-
TextSpan(
|
|
52
|
-
text: "${v.heading!.replaceAll("<br>", "\n")}\n",
|
|
53
|
-
style: theme.labelLarge,
|
|
54
|
-
),
|
|
55
|
-
TextSpan(text: "${v.index + 1} ", style: theme.labelMedium),
|
|
56
46
|
TextSpan(
|
|
47
|
+
style: baseStyle,
|
|
48
|
+
children: [
|
|
49
|
+
if (v.heading != null && v.heading!.isNotEmpty)
|
|
50
|
+
TextSpan(
|
|
51
|
+
text: "${v.heading!.replaceAll("<br>", "\n")}\n",
|
|
52
|
+
style: theme.labelLarge,
|
|
53
|
+
),
|
|
54
|
+
TextSpan(text: "${v.index + 1} ", style: theme.labelMedium),
|
|
55
|
+
TextSpan(
|
|
57
|
-
|
|
56
|
+
text: v.text ?? "",
|
|
58
|
-
|
|
57
|
+
style: appState.getHighlightStyle(context, v, false),
|
|
58
|
+
),
|
|
59
|
+
],
|
|
59
60
|
),
|
|
61
|
+
textScaler: TextScaler.linear(1.1 + textScale / 2),
|
|
60
|
-
|
|
62
|
+
),
|
|
61
63
|
),
|
|
62
|
-
textScaler: TextScaler.linear(1.1 + textScale / 2),
|
|
63
64
|
),
|
|
65
|
+
if (selectedVerses.isNotEmpty) const SizedBox(height: 120),
|
|
66
|
+
],
|
|
64
|
-
|
|
67
|
+
),
|
|
65
|
-
|
|
68
|
+
),
|
|
66
|
-
if (selectedVerses.isNotEmpty) const SizedBox(height: 120),
|
|
67
|
-
],
|
|
68
69
|
),
|
|
70
|
+
if (selectedVerses.isNotEmpty)
|
|
71
|
+
Positioned(
|
|
72
|
+
left: 20,
|
|
73
|
+
right: 20,
|
|
74
|
+
bottom: MediaQuery.of(context).padding.bottom + 40,
|
|
75
|
+
child: Center(child: ActionsSheet(bible: bible)),
|
|
69
|
-
|
|
76
|
+
),
|
|
77
|
+
],
|
|
70
78
|
);
|
|
71
79
|
}
|
|
72
80
|
}
|