~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.


assets/bibles/en_kjv.bin CHANGED
Binary file
lib/app.dart CHANGED
@@ -1,27 +1,71 @@
1
1
  import "package:async_redux/async_redux.dart" show Store, StoreProvider;
2
2
  import "package:flutter/material.dart";
3
+ import "package:go_router/go_router.dart";
3
4
  import "package:only_bible_app/home.dart";
5
+ import "package:only_bible_app/store/app_navigator.dart";
4
6
  import "package:only_bible_app/store/app_state.dart";
5
7
  import "package:only_bible_app/theme.dart";
6
8
  import "package:only_bible_app/utils.dart";
7
9
 
8
10
  class App extends StatelessWidget {
11
+ final GlobalKey<NavigatorState> globalNavigatorKey;
9
12
  final Store<AppState> store;
13
+ late final GoRouter _router;
10
14
 
11
- const App({super.key, required this.store});
15
+ App({super.key, required this.globalNavigatorKey, required this.store}) {
16
+ final s = store.state;
17
+ _router = GoRouter(
18
+ navigatorKey: globalNavigatorKey,
19
+ initialLocation: "/chapter/${s.savedBook}/${s.savedChapter}",
20
+ routes: [
21
+ GoRoute(
22
+ path: "/chapter/:bookIndex/:chapterIndex",
23
+ pageBuilder: (context, state) {
24
+ final bookIndex = int.parse(state.pathParameters["bookIndex"]!);
25
+ final chapterIndex = int.parse(state.pathParameters["chapterIndex"]!);
26
+ final slideDir = state.extra as TextDirection?;
27
+ if (slideDir != null) {
28
+ return CustomTransitionPage(
29
+ key: state.pageKey,
30
+ child: Home(bookIndex: bookIndex, chapterIndex: chapterIndex),
31
+ transitionsBuilder: (context, animation, secondaryAnimation, child) {
32
+ const begin = Offset(1.0, 0.0);
33
+ const end = Offset.zero;
34
+ const curve = Curves.ease;
35
+ final tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
36
+ return SlideTransition(
37
+ textDirection: slideDir,
38
+ position: animation.drive(tween),
39
+ child: child,
40
+ );
41
+ },
42
+ );
43
+ }
44
+ return NoTransitionPage(
45
+ key: state.pageKey,
46
+ child: Home(bookIndex: bookIndex, chapterIndex: chapterIndex),
47
+ );
48
+ },
49
+ ),
50
+ ],
51
+ );
52
+ }
12
53
 
13
54
  @override
14
55
  Widget build(BuildContext context) {
15
56
  return StoreProvider<AppState>(
16
57
  store: store,
58
+ child: AppRouterScope(
59
+ router: _router,
17
- child: Builder(
60
+ child: Builder(
18
- builder: (context) => MaterialApp(
61
+ builder: (context) => MaterialApp.router(
62
+ routerConfig: _router,
19
- title: "Bible",
63
+ title: "Bible",
20
- debugShowCheckedModeBanner: false,
64
+ debugShowCheckedModeBanner: false,
21
- themeMode: context.select((s) => s.darkMode) ? ThemeMode.dark : ThemeMode.light,
65
+ themeMode: context.select((s) => s.darkMode) ? ThemeMode.dark : ThemeMode.light,
22
- theme: lightTheme,
66
+ theme: lightTheme,
23
- darkTheme: darkTheme,
67
+ darkTheme: darkTheme,
24
- home: const Home(),
68
+ ),
25
69
  ),
26
70
  ),
27
71
  );
lib/home.dart CHANGED
@@ -4,13 +4,14 @@ import "package:only_bible_app/widgets/home_app_bar.dart";
4
4
  import "package:only_bible_app/widgets/verses_view.dart";
5
5
 
6
6
  class Home extends StatelessWidget {
7
+ final int bookIndex;
7
- const Home({super.key});
8
+ final int chapterIndex;
9
+
10
+ const Home({super.key, required this.bookIndex, required this.chapterIndex});
8
11
 
9
12
  @override
10
13
  Widget build(BuildContext context) {
11
14
  final bible = context.select((s) => s.bible);
12
- final bookIndex = context.select((s) => s.savedBook);
13
- final chapterIndex = context.select((s) => s.savedChapter);
14
15
  final book = bible.books![bookIndex];
15
16
  final chapter = book.chapters![chapterIndex];
16
17
  return Scaffold(
lib/main.dart CHANGED
@@ -1,4 +1,5 @@
1
1
  import "package:flutter/material.dart";
2
+ import "package:flutter/scheduler.dart";
2
3
  import "package:flutter/services.dart";
3
4
  import "package:flutter/foundation.dart";
4
5
  import "package:flutter_azure_tts/flutter_azure_tts.dart";
@@ -6,11 +7,14 @@ import "package:flutter_web_plugins/url_strategy.dart";
6
7
  import "package:flutter_native_splash/flutter_native_splash.dart";
7
8
  import "package:async_redux/async_redux.dart";
8
9
  import "package:only_bible_app/app.dart";
10
+ import "package:only_bible_app/dialog.dart";
9
11
 
10
12
  import "package:only_bible_app/store/app_persistor.dart";
11
13
  import "package:only_bible_app/store/app_state.dart";
12
14
  import "package:only_bible_app/utils.dart";
13
15
 
16
+ final navigatorKey = GlobalKey<NavigatorState>();
17
+
14
18
  void updateStatusBar(bool v) {
15
19
  if (v) {
16
20
  SystemChrome.setSystemUIOverlayStyle(
@@ -37,25 +41,24 @@ void main() async {
37
41
  FlutterNativeSplash.preserve(
38
42
  widgetsBinding: WidgetsFlutterBinding.ensureInitialized(),
39
43
  );
40
- // FlutterError.onError = (errorDetails) {
44
+ FlutterError.onError = (errorDetails) {
45
+ FlutterError.presentError(errorDetails);
41
- // SchedulerBinding.instance.addPostFrameCallback((d) {
46
+ SchedulerBinding.instance.addPostFrameCallback((d) {
42
- // showReportError(
47
+ showReportError(
43
- // globalNavigatorKey.currentState!.context,
48
+ navigatorKey.currentState!.context,
44
- // errorDetails.exception.toString(),
49
+ errorDetails.exception.toString(),
45
- // errorDetails.stack,
50
+ errorDetails.stack,
46
- // );
51
+ );
47
- // });
52
+ });
48
- // };
53
+ };
49
- // PlatformDispatcher.instance.onError = (error, stack) {
54
+ PlatformDispatcher.instance.onError = (error, stack) {
50
- // Future.delayed(const Duration(seconds: 1), () {
51
- // showReportError(
55
+ showReportError(
52
- // globalNavigatorKey.currentState!.context,
56
+ navigatorKey.currentState!.context,
53
- // error.toString(),
57
+ error.toString(),
54
- // stack,
58
+ stack,
55
- // );
56
- // });
59
+ );
57
- // return true;
60
+ return true;
58
- // };
61
+ };
59
62
  usePathUrlStrategy();
60
63
  FlutterAzureTts.init(
61
64
  subscriptionKey: "a9d2d78796924a2a9df2b6d5c1c4a576",
@@ -72,6 +75,6 @@ void main() async {
72
75
  persistor: persistor,
73
76
  );
74
77
  updateStatusBar(store.state.darkMode);
75
- runApp(App(store: store));
78
+ runApp(App(globalNavigatorKey: navigatorKey, store: store));
76
79
  FlutterNativeSplash.remove();
77
80
  }
lib/store/actions_navigation.dart CHANGED
@@ -1,5 +1,6 @@
1
1
  import "package:async_redux/async_redux.dart";
2
2
  import "package:flutter/material.dart";
3
+ import "package:go_router/go_router.dart";
3
4
  import "package:only_bible_app/gen/bible.gen.dart";
4
5
  import "package:only_bible_app/store/actions_state.dart" show audioPlayer;
5
6
  import "package:only_bible_app/store/app_state.dart";
@@ -31,9 +32,10 @@ class ShowSettingsAction extends ReduxAction<AppState> {
31
32
 
32
33
  class ShowBookSelectAction extends ReduxAction<AppState> {
33
34
  final BuildContext buildContext;
35
+ final GoRouter router;
34
36
  final Bible bible;
35
37
 
36
- ShowBookSelectAction(this.buildContext, this.bible);
38
+ ShowBookSelectAction(this.buildContext, this.router, this.bible);
37
39
 
38
40
  @override
39
41
  AppState? reduce() {
@@ -51,9 +53,9 @@ class ShowBookSelectAction extends ReduxAction<AppState> {
51
53
  final book = bible.books![index];
52
54
  Navigator.of(sheetContext).pop();
53
55
  if (book.chapters!.length == 1) {
54
- dispatch(GoToChapterAction(index, 0));
56
+ dispatch(GoToChapterAction(router, index, 0));
55
57
  } else {
56
- dispatch(ShowChapterSelectAction(buildContext, bible, book));
58
+ dispatch(ShowChapterSelectAction(buildContext, router, bible, book));
57
59
  }
58
60
  },
59
61
  );
@@ -65,10 +67,11 @@ class ShowBookSelectAction extends ReduxAction<AppState> {
65
67
 
66
68
  class ShowChapterSelectAction extends ReduxAction<AppState> {
67
69
  final BuildContext buildContext;
70
+ final GoRouter router;
68
71
  final Bible bible;
69
72
  final Book book;
70
73
 
71
- ShowChapterSelectAction(this.buildContext, this.bible, this.book);
74
+ ShowChapterSelectAction(this.buildContext, this.router, this.bible, this.book);
72
75
 
73
76
  @override
74
77
  AppState? reduce() {
@@ -91,14 +94,16 @@ class ShowChapterSelectAction extends ReduxAction<AppState> {
91
94
  }
92
95
 
93
96
  class GoToChapterAction extends ReduxAction<AppState> {
97
+ final GoRouter router;
94
98
  final int book;
95
99
  final int chapter;
96
100
 
97
- GoToChapterAction(this.book, this.chapter);
101
+ GoToChapterAction(this.router, this.book, this.chapter);
98
102
 
99
103
  @override
100
104
  AppState reduce() {
101
105
  audioPlayer.pause();
106
+ router.push("/chapter/$book/$chapter");
102
107
  return state.copy(
103
108
  savedBook: book,
104
109
  savedChapter: chapter,
@@ -108,11 +113,12 @@ class GoToChapterAction extends ReduxAction<AppState> {
108
113
  }
109
114
 
110
115
  class NextChapterAction extends ReduxAction<AppState> {
116
+ final GoRouter router;
111
117
  final Bible bible;
112
118
  final int book;
113
119
  final int chapter;
114
120
 
115
- NextChapterAction(this.bible, this.book, this.chapter);
121
+ NextChapterAction(this.router, this.bible, this.book, this.chapter);
116
122
 
117
123
  @override
118
124
  AppState? reduce() {
@@ -131,6 +137,10 @@ class NextChapterAction extends ReduxAction<AppState> {
131
137
 
132
138
  if (newBook == null) return null;
133
139
  audioPlayer.pause();
140
+ router.pushReplacement(
141
+ "/chapter/$newBook/$newChapter",
142
+ extra: TextDirection.ltr,
143
+ );
134
144
  return state.copy(
135
145
  savedBook: newBook,
136
146
  savedChapter: newChapter,
@@ -140,11 +150,12 @@ class NextChapterAction extends ReduxAction<AppState> {
140
150
  }
141
151
 
142
152
  class PreviousChapterAction extends ReduxAction<AppState> {
153
+ final GoRouter router;
143
154
  final Bible bible;
144
155
  final int book;
145
156
  final int chapter;
146
157
 
147
- PreviousChapterAction(this.bible, this.book, this.chapter);
158
+ PreviousChapterAction(this.router, this.bible, this.book, this.chapter);
148
159
 
149
160
  @override
150
161
  AppState? reduce() {
@@ -161,8 +172,17 @@ class PreviousChapterAction extends ReduxAction<AppState> {
161
172
  newChapter = prevBook.chapters!.length - 1;
162
173
  }
163
174
 
175
+ if (router.canPop()) {
176
+ router.pop();
177
+ return null;
178
+ }
179
+
164
180
  if (newBook == null) return null;
165
181
  audioPlayer.pause();
182
+ router.pushReplacement(
183
+ "/chapter/$newBook/$newChapter",
184
+ extra: TextDirection.rtl,
185
+ );
166
186
  return state.copy(
167
187
  savedBook: newBook,
168
188
  savedChapter: newChapter,
lib/store/app_navigator.dart ADDED
@@ -0,0 +1,21 @@
1
+ import "package:flutter/material.dart";
2
+ import "package:go_router/go_router.dart";
3
+
4
+ class AppRouterScope extends InheritedWidget {
5
+ final GoRouter router;
6
+
7
+ const AppRouterScope({
8
+ super.key,
9
+ required this.router,
10
+ required super.child,
11
+ });
12
+
13
+ static AppRouterScope of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<AppRouterScope>()!;
14
+
15
+ @override
16
+ bool updateShouldNotify(AppRouterScope old) => false;
17
+ }
18
+
19
+ extension AppRouterContext on BuildContext {
20
+ GoRouter get router => AppRouterScope.of(this).router;
21
+ }
lib/widgets/chapter_select_sheet.dart CHANGED
@@ -1,6 +1,7 @@
1
1
  import "package:flutter/material.dart";
2
2
  import "package:only_bible_app/gen/bible.gen.dart";
3
3
  import "package:only_bible_app/store/actions_navigation.dart";
4
+ import "package:only_bible_app/store/app_navigator.dart";
4
5
  import "package:only_bible_app/utils.dart";
5
6
  import "package:only_bible_app/widgets/book_tile.dart";
6
7
 
@@ -46,7 +47,7 @@ class ChapterSelectSheet extends StatelessWidget {
46
47
  onTap: () {
47
48
  Navigator.of(sheetContext).pop();
48
49
  parentContext.dispatch(
49
- GoToChapterAction(book.index, index),
50
+ GoToChapterAction(parentContext.router, book.index, index),
50
51
  );
51
52
  },
52
53
  );
lib/widgets/home_app_bar.dart CHANGED
@@ -2,6 +2,7 @@ import "package:flutter/material.dart";
2
2
  import "package:only_bible_app/dialog.dart";
3
3
  import "package:only_bible_app/gen/bible.gen.dart";
4
4
  import "package:only_bible_app/store/actions_navigation.dart";
5
+ import "package:only_bible_app/store/app_navigator.dart";
5
6
  import "package:only_bible_app/utils.dart";
6
7
 
7
8
  class HomeAppBar extends StatelessWidget implements PreferredSizeWidget {
@@ -31,7 +32,7 @@ class HomeAppBar extends StatelessWidget implements PreferredSizeWidget {
31
32
  InkWell(
32
33
  key: const Key("bookTitle"),
33
34
  enableFeedback: true,
34
- onTap: () => context.dispatch(ShowBookSelectAction(context, bible)),
35
+ onTap: () => context.dispatch(ShowBookSelectAction(context, context.router, bible)),
35
36
  child: Text(
36
37
  book.name!,
37
38
  style: Theme.of(context).textTheme.headlineMedium,
@@ -40,7 +41,7 @@ class HomeAppBar extends StatelessWidget implements PreferredSizeWidget {
40
41
  InkWell(
41
42
  key: const Key("chapterTitle"),
42
43
  enableFeedback: true,
43
- onTap: () => context.dispatch(ShowChapterSelectAction(context, bible, book)),
44
+ onTap: () => context.dispatch(ShowChapterSelectAction(context, context.router, bible, book)),
44
45
  child: Padding(
45
46
  padding: const EdgeInsets.only(left: 16, right: 16),
46
47
  child: Text(
lib/widgets/verses_view.dart CHANGED
@@ -3,6 +3,7 @@ import "package:flutter/material.dart";
3
3
  import "package:flutter_swipe_detector/flutter_swipe_detector.dart";
4
4
  import "package:only_bible_app/gen/bible.gen.dart";
5
5
  import "package:only_bible_app/store/actions_navigation.dart";
6
+ import "package:only_bible_app/store/app_navigator.dart";
6
7
  import "package:only_bible_app/widgets/menu_overlay.dart";
7
8
  import "package:only_bible_app/store/actions_state.dart";
8
9
  import "package:only_bible_app/utils.dart";
@@ -26,8 +27,10 @@ class VersesView extends StatelessWidget {
26
27
  return Stack(
27
28
  children: [
28
29
  SwipeDetector(
30
+ onSwipeLeft: (offset) =>
29
- onSwipeLeft: (offset) => context.dispatch(NextChapterAction(bible, chapter.book, chapter.index)),
31
+ context.dispatch(NextChapterAction(context.router, bible, chapter.book, chapter.index)),
32
+ onSwipeRight: (offset) =>
30
- onSwipeRight: (offset) => context.dispatch(PreviousChapterAction(bible, chapter.book, chapter.index)),
33
+ context.dispatch(PreviousChapterAction(context.router, bible, chapter.book, chapter.index)),
31
34
  child: SingleChildScrollView(
32
35
  key: ValueKey("${chapter.book}-${chapter.index}"),
33
36
  physics: const BouncingScrollPhysics(),
@@ -102,7 +105,7 @@ class VersesView extends StatelessWidget {
102
105
  decoration: TextDecoration.underline,
103
106
  ),
104
107
  recognizer: TapGestureRecognizer()
105
- ..onTap = () => context.dispatch(GoToChapterAction(bookIndex, chapterIndex)),
108
+ ..onTap = () => context.dispatch(GoToChapterAction(context.router, bookIndex, chapterIndex)),
106
109
  ),
107
110
  );
108
111
  lastEnd = match.end;
pubspec.lock CHANGED
@@ -434,6 +434,14 @@ packages:
434
434
  url: "https://pub.dev"
435
435
  source: hosted
436
436
  version: "2.1.3"
437
+ go_router:
438
+ dependency: "direct main"
439
+ description:
440
+ name: go_router
441
+ sha256: "7974313e217a7771557add6ff2238acb63f635317c35fa590d348fb238f00896"
442
+ url: "https://pub.dev"
443
+ source: hosted
444
+ version: "17.1.0"
437
445
  golden_screenshot:
438
446
  dependency: "direct dev"
439
447
  description:
pubspec.yaml CHANGED
@@ -24,6 +24,7 @@ dependencies:
24
24
  http: ^1.6.0
25
25
  async_redux: ^27.1.1
26
26
  flat_buffers: ^25.9.23
27
+ go_router: ^17.1.0
27
28
 
28
29
  dev_dependencies:
29
30
  flutter_test: