~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.
6a3f2579
—
pyrossh 1 month ago
update stuff
- lib/app.dart +83 -101
- lib/main.dart +14 -1
- lib/navigation.dart +26 -20
- lib/screens/bible_select_screen.dart +5 -6
- lib/screens/book_select_screen.dart +33 -49
- lib/screens/chapter_select_screen.dart +23 -37
- lib/screens/chapter_view_screen.dart +19 -38
- lib/sheets/actions_sheet.dart +36 -57
- lib/sheets/settings_sheet.dart +11 -18
- lib/store/actions.dart +14 -7
- lib/store/app_state.dart +14 -18
- lib/theme.dart +7 -1
- lib/utils.dart +12 -13
- lib/widgets/sliver_heading.dart +4 -2
- lib/widgets/verses_view.dart +8 -22
lib/app.dart
CHANGED
|
@@ -8,110 +8,13 @@ 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
10
|
import "package:only_bible_app/store/app_state.dart";
|
|
11
|
-
import "package:only_bible_app/store/app_persistor.dart";
|
|
12
11
|
import "package:only_bible_app/theme.dart";
|
|
13
12
|
import "package:only_bible_app/utils.dart";
|
|
14
13
|
|
|
15
|
-
final globalNavigatorKey = GlobalKey<NavigatorState>();
|
|
16
|
-
final persistor = AppPersistor();
|
|
17
|
-
final Store<AppState> store = Store<AppState>(
|
|
18
|
-
initialState: const AppState(),
|
|
19
|
-
persistor: persistor,
|
|
20
|
-
);
|
|
21
|
-
final GoRouter router = buildRouter();
|
|
22
|
-
|
|
23
|
-
GoRouter buildRouter() {
|
|
24
|
-
final s = store.state;
|
|
25
|
-
return GoRouter(
|
|
26
|
-
navigatorKey: globalNavigatorKey,
|
|
27
|
-
initialLocation: s.firstOpen
|
|
28
|
-
? "/bible"
|
|
29
|
-
: "/chapter/${Uri.encodeComponent(s.bibleName)}/${s.savedBook}/${s.savedChapter}",
|
|
30
|
-
routes: [
|
|
31
|
-
GoRoute(
|
|
32
|
-
path: "/bible",
|
|
33
|
-
pageBuilder: (context, state) => const NoTransitionPage(
|
|
34
|
-
child: BibleSelectScreen(),
|
|
35
|
-
),
|
|
36
|
-
),
|
|
37
|
-
GoRoute(
|
|
38
|
-
path: "/chapter/:bibleName/:bookIndex/:chapterIndex",
|
|
39
|
-
pageBuilder: (context, state) {
|
|
40
|
-
final bibleName =
|
|
41
|
-
Uri.decodeComponent(state.pathParameters["bibleName"]!);
|
|
42
|
-
final bookIndex = int.parse(state.pathParameters["bookIndex"]!);
|
|
43
|
-
final chapterIndex = int.parse(state.pathParameters["chapterIndex"]!);
|
|
44
|
-
final slideDir = state.extra as TextDirection?;
|
|
45
|
-
if (slideDir != null) {
|
|
46
|
-
return CustomTransitionPage(
|
|
47
|
-
key: state.pageKey,
|
|
48
|
-
child: ChapterViewScreen(
|
|
49
|
-
bibleName: bibleName,
|
|
50
|
-
bookIndex: bookIndex,
|
|
51
|
-
chapterIndex: chapterIndex,
|
|
52
|
-
),
|
|
53
|
-
transitionsBuilder:
|
|
54
|
-
(context, animation, secondaryAnimation, child) {
|
|
55
|
-
const begin = Offset(1.0, 0.0);
|
|
56
|
-
const end = Offset.zero;
|
|
57
|
-
const curve = Curves.ease;
|
|
58
|
-
final tween = Tween(begin: begin, end: end)
|
|
59
|
-
.chain(CurveTween(curve: curve));
|
|
60
|
-
return SlideTransition(
|
|
61
|
-
textDirection: slideDir,
|
|
62
|
-
position: animation.drive(tween),
|
|
63
|
-
child: child,
|
|
64
|
-
);
|
|
65
|
-
},
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
return NoTransitionPage(
|
|
69
|
-
key: state.pageKey,
|
|
70
|
-
child: ChapterViewScreen(
|
|
71
|
-
bibleName: bibleName,
|
|
72
|
-
bookIndex: bookIndex,
|
|
73
|
-
chapterIndex: chapterIndex,
|
|
74
|
-
),
|
|
75
|
-
);
|
|
76
|
-
},
|
|
77
|
-
),
|
|
78
|
-
GoRoute(
|
|
79
|
-
path: "/books/:bibleName",
|
|
80
|
-
pageBuilder: (context, state) {
|
|
81
|
-
final bibleName =
|
|
82
|
-
Uri.decodeComponent(state.pathParameters["bibleName"]!);
|
|
83
|
-
return NoTransitionPage(
|
|
84
|
-
child: BookSelectScreen(bibleName: bibleName),
|
|
85
|
-
);
|
|
86
|
-
},
|
|
87
|
-
),
|
|
88
|
-
GoRoute(
|
|
89
|
-
path: "/chapters/:bibleName/:bookIndex",
|
|
90
|
-
pageBuilder: (context, state) {
|
|
91
|
-
final bibleName =
|
|
92
|
-
Uri.decodeComponent(state.pathParameters["bibleName"]!);
|
|
93
|
-
final bookIndex = int.parse(state.pathParameters["bookIndex"]!);
|
|
94
|
-
return NoTransitionPage(
|
|
95
|
-
child:
|
|
96
|
-
ChapterSelectScreen(bibleName: bibleName, bookIndex: bookIndex),
|
|
97
|
-
);
|
|
98
|
-
},
|
|
99
|
-
),
|
|
100
|
-
GoRoute(
|
|
101
|
-
path: "/webview",
|
|
102
|
-
pageBuilder: (context, state) {
|
|
103
|
-
final url = state.extra as String;
|
|
104
|
-
return NoTransitionPage(
|
|
105
|
-
child: WebViewScreen(url: url),
|
|
106
|
-
);
|
|
107
|
-
},
|
|
108
|
-
),
|
|
109
|
-
],
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
14
|
class App extends StatelessWidget {
|
|
15
|
+
final GlobalKey<NavigatorState> globalNavigatorKey;
|
|
114
|
-
|
|
16
|
+
final Store<AppState> store;
|
|
17
|
+
const App({super.key, required this.globalNavigatorKey, required this.store});
|
|
115
18
|
|
|
116
19
|
@override
|
|
117
20
|
Widget build(BuildContext context) {
|
|
@@ -120,7 +23,86 @@ class App extends StatelessWidget {
|
|
|
120
23
|
child: StoreConnector<AppState, _AppVm>(
|
|
121
24
|
vm: () => _AppVmFactory(),
|
|
122
25
|
builder: (context, vm) => MaterialApp.router(
|
|
123
|
-
routerConfig:
|
|
26
|
+
routerConfig: GoRouter(
|
|
27
|
+
navigatorKey: globalNavigatorKey,
|
|
28
|
+
initialLocation: context.state.firstOpen
|
|
29
|
+
? "/bible"
|
|
30
|
+
: "/chapter/${Uri.encodeComponent(context.state.bibleName)}/${context.state.savedBook}/${context.state.savedChapter}",
|
|
31
|
+
routes: [
|
|
32
|
+
GoRoute(
|
|
33
|
+
path: "/bible",
|
|
34
|
+
pageBuilder: (context, state) => const NoTransitionPage(
|
|
35
|
+
child: BibleSelectScreen(),
|
|
36
|
+
),
|
|
37
|
+
),
|
|
38
|
+
GoRoute(
|
|
39
|
+
path: "/chapter/:bibleName/:bookIndex/:chapterIndex",
|
|
40
|
+
pageBuilder: (context, state) {
|
|
41
|
+
final bibleName = Uri.decodeComponent(state.pathParameters["bibleName"]!);
|
|
42
|
+
final bookIndex = int.parse(state.pathParameters["bookIndex"]!);
|
|
43
|
+
final chapterIndex = int.parse(state.pathParameters["chapterIndex"]!);
|
|
44
|
+
final slideDir = state.extra as TextDirection?;
|
|
45
|
+
if (slideDir != null) {
|
|
46
|
+
return CustomTransitionPage(
|
|
47
|
+
key: state.pageKey,
|
|
48
|
+
child: ChapterViewScreen(
|
|
49
|
+
bibleName: bibleName,
|
|
50
|
+
bookIndex: bookIndex,
|
|
51
|
+
chapterIndex: chapterIndex,
|
|
52
|
+
),
|
|
53
|
+
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
|
54
|
+
const begin = Offset(1.0, 0.0);
|
|
55
|
+
const end = Offset.zero;
|
|
56
|
+
const curve = Curves.ease;
|
|
57
|
+
final tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
|
|
58
|
+
return SlideTransition(
|
|
59
|
+
textDirection: slideDir,
|
|
60
|
+
position: animation.drive(tween),
|
|
61
|
+
child: child,
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return NoTransitionPage(
|
|
67
|
+
key: state.pageKey,
|
|
68
|
+
child: ChapterViewScreen(
|
|
69
|
+
bibleName: bibleName,
|
|
70
|
+
bookIndex: bookIndex,
|
|
71
|
+
chapterIndex: chapterIndex,
|
|
72
|
+
),
|
|
73
|
+
);
|
|
74
|
+
},
|
|
75
|
+
),
|
|
76
|
+
GoRoute(
|
|
77
|
+
path: "/books/:bibleName",
|
|
78
|
+
pageBuilder: (context, state) {
|
|
79
|
+
final bibleName = Uri.decodeComponent(state.pathParameters["bibleName"]!);
|
|
80
|
+
return NoTransitionPage(
|
|
81
|
+
child: BookSelectScreen(bibleName: bibleName),
|
|
82
|
+
);
|
|
83
|
+
},
|
|
84
|
+
),
|
|
85
|
+
GoRoute(
|
|
86
|
+
path: "/chapters/:bibleName/:bookIndex",
|
|
87
|
+
pageBuilder: (context, state) {
|
|
88
|
+
final bibleName = Uri.decodeComponent(state.pathParameters["bibleName"]!);
|
|
89
|
+
final bookIndex = int.parse(state.pathParameters["bookIndex"]!);
|
|
90
|
+
return NoTransitionPage(
|
|
91
|
+
child: ChapterSelectScreen(bibleName: bibleName, bookIndex: bookIndex),
|
|
92
|
+
);
|
|
93
|
+
},
|
|
94
|
+
),
|
|
95
|
+
GoRoute(
|
|
96
|
+
path: "/webview",
|
|
97
|
+
pageBuilder: (context, state) {
|
|
98
|
+
final url = state.extra as String;
|
|
99
|
+
return NoTransitionPage(
|
|
100
|
+
child: WebViewScreen(url: url),
|
|
101
|
+
);
|
|
102
|
+
},
|
|
103
|
+
),
|
|
104
|
+
],
|
|
105
|
+
),
|
|
124
106
|
onGenerateTitle: (context) => context.l.title,
|
|
125
107
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
126
108
|
supportedLocales: AppLocalizations.supportedLocales,
|
lib/main.dart
CHANGED
|
@@ -4,12 +4,24 @@ import "package:flutter/scheduler.dart";
|
|
|
4
4
|
import "package:flutter_azure_tts/flutter_azure_tts.dart";
|
|
5
5
|
import "package:flutter_web_plugins/url_strategy.dart";
|
|
6
6
|
import "package:flutter_native_splash/flutter_native_splash.dart";
|
|
7
|
+
import "package:async_redux/async_redux.dart";
|
|
7
8
|
import "package:only_bible_app/app.dart";
|
|
8
9
|
import "package:only_bible_app/dialog.dart";
|
|
9
10
|
import "package:only_bible_app/env.dart";
|
|
10
11
|
import "package:only_bible_app/navigation.dart";
|
|
12
|
+
import "package:only_bible_app/store/actions.dart";
|
|
13
|
+
import "package:only_bible_app/store/app_persistor.dart";
|
|
14
|
+
import "package:only_bible_app/store/app_state.dart";
|
|
15
|
+
import "package:only_bible_app/store/actions.dart";
|
|
11
16
|
|
|
12
17
|
void main() async {
|
|
18
|
+
final globalNavigatorKey = GlobalKey<NavigatorState>();
|
|
19
|
+
final persistor = AppPersistor();
|
|
20
|
+
final initialState = await persistor.readState();
|
|
21
|
+
final store = Store<AppState>(
|
|
22
|
+
initialState: initialState ?? const AppState(),
|
|
23
|
+
persistor: persistor,
|
|
24
|
+
);
|
|
13
25
|
// await store.persistor.readState();
|
|
14
26
|
// FlutterError.onError = (errorDetails) {
|
|
15
27
|
// SchedulerBinding.instance.addPostFrameCallback((d) {
|
|
@@ -41,6 +53,7 @@ void main() async {
|
|
|
41
53
|
withLogs: true,
|
|
42
54
|
);
|
|
43
55
|
updateStatusBar(store.state.darkMode);
|
|
44
|
-
|
|
56
|
+
await store.dispatchAndWait(LoadBibleAction());
|
|
57
|
+
runApp(App(globalNavigatorKey: globalNavigatorKey, store: store));
|
|
45
58
|
// FlutterNativeSplash.remove();
|
|
46
59
|
}
|
lib/navigation.dart
CHANGED
|
@@ -7,7 +7,6 @@ import "package:only_bible_app/sheets/actions_sheet.dart";
|
|
|
7
7
|
import "package:only_bible_app/sheets/settings_sheet.dart";
|
|
8
8
|
import "package:only_bible_app/store/actions.dart";
|
|
9
9
|
import "package:only_bible_app/store/app_state.dart";
|
|
10
|
-
import "package:only_bible_app/app.dart";
|
|
11
10
|
import "package:only_bible_app/utils.dart";
|
|
12
11
|
import "package:share_plus/share_plus.dart";
|
|
13
12
|
|
|
@@ -44,10 +43,10 @@ void pushBookChapter(
|
|
|
44
43
|
int chapter,
|
|
45
44
|
TextDirection? dir,
|
|
46
45
|
) {
|
|
47
|
-
|
|
46
|
+
context.dispatch(UpdateChapterAction(book, chapter));
|
|
48
|
-
if (
|
|
47
|
+
if (context.state.isPlaying) {
|
|
49
48
|
AppState.player.pause();
|
|
50
|
-
|
|
49
|
+
context.dispatch(SetPlayingAction(false));
|
|
51
50
|
}
|
|
52
51
|
hideActions(context);
|
|
53
52
|
context.push(_chapterPath(bibleName, book, chapter), extra: dir);
|
|
@@ -59,10 +58,10 @@ void replaceBookChapter(
|
|
|
59
58
|
int book,
|
|
60
59
|
int chapter,
|
|
61
60
|
) {
|
|
62
|
-
|
|
61
|
+
context.dispatch(UpdateChapterAction(book, chapter));
|
|
63
|
-
if (
|
|
62
|
+
if (context.state.isPlaying) {
|
|
64
63
|
AppState.player.pause();
|
|
65
|
-
|
|
64
|
+
context.dispatch(SetPlayingAction(false));
|
|
66
65
|
}
|
|
67
66
|
hideActions(context);
|
|
68
67
|
context.go(_chapterPath(bibleName, book, chapter));
|
|
@@ -156,7 +155,8 @@ Future<void> updateCurrentBible(
|
|
|
156
155
|
int chapter,
|
|
157
156
|
) async {
|
|
158
157
|
hideActions(context);
|
|
159
|
-
|
|
158
|
+
context.dispatch(UpdateBibleAction(name, code));
|
|
159
|
+
await context.dispatchAndWait(LoadBibleAction());
|
|
160
160
|
context.go(_chapterPath(name, book, chapter));
|
|
161
161
|
}
|
|
162
162
|
|
|
@@ -178,13 +178,11 @@ Future<void> rateApp(BuildContext context) async {
|
|
|
178
178
|
AppReview.requestReview;
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
Future<void> shareVerses(
|
|
182
|
-
|
|
181
|
+
Future<void> shareVerses(BuildContext context, Bible bible, List<Verse> verses) async {
|
|
183
182
|
final name = context.bookNames[verses.first.book];
|
|
184
183
|
final chapter = verses.first.chapter + 1;
|
|
185
184
|
final items = verses.sortedBy((e) => e.index).map((e) => e.index + 1);
|
|
186
|
-
final versesThrough =
|
|
187
|
-
|
|
185
|
+
final versesThrough = items.length >= 3 ? "${items.first}-${items.last}" : items.join(",");
|
|
188
186
|
final version = context.currentLang.languageCode == "en" ? "KJV" : "";
|
|
189
187
|
final title = "$name $chapter:$versesThrough $version";
|
|
190
188
|
final text = verses.map((e) => e.text).join("\n");
|
|
@@ -209,18 +207,26 @@ void showSettings(BuildContext context, Bible bible) {
|
|
|
209
207
|
);
|
|
210
208
|
}
|
|
211
209
|
|
|
212
|
-
|
|
210
|
+
OverlayEntry? _actionsOverlay;
|
|
213
211
|
|
|
214
212
|
void showActions(BuildContext context, Bible bible) {
|
|
213
|
+
_actionsOverlay?.remove();
|
|
214
|
+
_actionsOverlay = null;
|
|
215
|
-
|
|
215
|
+
final overlay = Overlay.of(context);
|
|
216
|
-
|
|
216
|
+
_actionsOverlay = OverlayEntry(
|
|
217
|
+
builder: (context) => Align(
|
|
218
|
+
alignment: Alignment.bottomCenter,
|
|
217
|
-
|
|
219
|
+
child: Padding(
|
|
220
|
+
padding: const EdgeInsets.only(bottom: 40, left: 20, right: 20),
|
|
218
|
-
|
|
221
|
+
child: ActionsSheet(bible: bible),
|
|
222
|
+
),
|
|
223
|
+
),
|
|
219
224
|
);
|
|
225
|
+
overlay.insert(_actionsOverlay!);
|
|
220
226
|
}
|
|
221
227
|
|
|
222
228
|
void hideActions(BuildContext context) {
|
|
223
|
-
|
|
229
|
+
_actionsOverlay?.remove();
|
|
224
|
-
|
|
230
|
+
_actionsOverlay = null;
|
|
225
|
-
|
|
231
|
+
context.dispatch(ClearSelectedVersesAction());
|
|
226
232
|
}
|
lib/screens/bible_select_screen.dart
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import "package:flutter/material.dart";
|
|
2
2
|
import "package:only_bible_app/navigation.dart";
|
|
3
3
|
import "package:only_bible_app/store/actions.dart";
|
|
4
|
-
import "package:only_bible_app/app.dart";
|
|
5
4
|
import "package:only_bible_app/utils.dart";
|
|
6
5
|
import "package:only_bible_app/widgets/scaffold_menu.dart";
|
|
7
6
|
import "package:only_bible_app/widgets/sliver_heading.dart";
|
|
@@ -18,7 +17,7 @@ class BibleSelectScreen extends StatelessWidget {
|
|
|
18
17
|
slivers: [
|
|
19
18
|
SliverHeading(
|
|
20
19
|
title: context.l.bibleSelectTitle,
|
|
21
|
-
showClose: !
|
|
20
|
+
showClose: !context.state.firstOpen,
|
|
22
21
|
),
|
|
23
22
|
SliverTileGrid(
|
|
24
23
|
listType: ListType.extraLarge,
|
|
@@ -35,15 +34,15 @@ class BibleSelectScreen extends StatelessWidget {
|
|
|
35
34
|
)
|
|
36
35
|
: Text(l.languageTitle, textScaleFactor: 1.1),
|
|
37
36
|
onPressed: () {
|
|
38
|
-
if (
|
|
37
|
+
if (context.state.firstOpen) {
|
|
39
|
-
|
|
38
|
+
context.dispatch(FirstOpenDoneAction());
|
|
40
39
|
}
|
|
41
40
|
updateCurrentBible(
|
|
42
41
|
context,
|
|
43
42
|
l.languageTitle,
|
|
44
43
|
l.localeName,
|
|
45
|
-
|
|
44
|
+
context.state.savedBook,
|
|
46
|
-
|
|
45
|
+
context.state.savedChapter,
|
|
47
46
|
);
|
|
48
47
|
},
|
|
49
48
|
);
|
lib/screens/book_select_screen.dart
CHANGED
|
@@ -22,57 +22,41 @@ class BookSelectScreen extends StatelessWidget {
|
|
|
22
22
|
|
|
23
23
|
@override
|
|
24
24
|
Widget build(BuildContext context) {
|
|
25
|
-
return FutureBuilder(
|
|
26
|
-
future: loadBible(bibleName),
|
|
27
|
-
|
|
25
|
+
final bible = context.state.bible;
|
|
26
|
+
if (bible == null) {
|
|
28
|
-
|
|
27
|
+
return ColoredBox(
|
|
29
|
-
loading: () => ColoredBox(
|
|
30
|
-
|
|
28
|
+
color: Theme.of(context).colorScheme.surface,
|
|
31
|
-
|
|
29
|
+
child: const Center(child: CircularProgressIndicator()),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return ScaffoldMenu(
|
|
33
|
+
child: CustomScrollView(
|
|
34
|
+
physics: const BouncingScrollPhysics(),
|
|
35
|
+
slivers: [
|
|
36
|
+
SliverHeading(title: context.l.oldTestamentTitle, showClose: true),
|
|
37
|
+
SliverTileGrid(
|
|
38
|
+
children: List.of(
|
|
39
|
+
bible.getOldBooks().map((book) {
|
|
40
|
+
return TextButton(
|
|
41
|
+
child: Text(book.shortName(context.bookNames[book.index])),
|
|
42
|
+
onPressed: () => onBookSelected(context, bible, book.index),
|
|
43
|
+
);
|
|
44
|
+
}),
|
|
45
|
+
),
|
|
32
46
|
),
|
|
33
|
-
success: (Bible? bible) {
|
|
34
|
-
return ScaffoldMenu(
|
|
35
|
-
child: CustomScrollView(
|
|
36
|
-
physics: const BouncingScrollPhysics(),
|
|
37
|
-
slivers: [
|
|
38
|
-
SliverHeading(
|
|
39
|
-
|
|
47
|
+
SliverHeading(title: context.l.newTestamentTitle, top: 30, bottom: 20),
|
|
40
|
-
|
|
48
|
+
SliverTileGrid(
|
|
41
|
-
|
|
49
|
+
children: List.of(
|
|
42
|
-
|
|
50
|
+
bible.getNewBooks().map((book) {
|
|
43
|
-
|
|
51
|
+
return TextButton(
|
|
44
|
-
child: Text(
|
|
45
|
-
|
|
52
|
+
child: Text(book.shortName(context.bookNames[book.index])),
|
|
46
|
-
onPressed: () =>
|
|
47
|
-
|
|
53
|
+
onPressed: () => onBookSelected(context, bible, book.index),
|
|
48
|
-
|
|
54
|
+
);
|
|
49
|
-
|
|
55
|
+
}),
|
|
50
|
-
|
|
56
|
+
),
|
|
51
|
-
),
|
|
52
|
-
SliverHeading(
|
|
53
|
-
title: context.l.newTestamentTitle, top: 30, bottom: 20),
|
|
54
|
-
SliverTileGrid(
|
|
55
|
-
children: List.of(
|
|
56
|
-
bible.getNewBooks().map((book) {
|
|
57
|
-
return TextButton(
|
|
58
|
-
child: Text(
|
|
59
|
-
book.shortName(context.bookNames[book.index])),
|
|
60
|
-
onPressed: () =>
|
|
61
|
-
onBookSelected(context, bible, book.index),
|
|
62
|
-
);
|
|
63
|
-
}),
|
|
64
|
-
),
|
|
65
|
-
),
|
|
66
|
-
],
|
|
67
|
-
),
|
|
68
|
-
);
|
|
69
|
-
},
|
|
70
|
-
error: () => ColoredBox(
|
|
71
|
-
color: Theme.of(context).colorScheme.surface,
|
|
72
|
-
child: Center(child: Text("Could not load bible ${state.error}")),
|
|
73
57
|
),
|
|
58
|
+
],
|
|
74
|
-
|
|
59
|
+
),
|
|
75
|
-
},
|
|
76
60
|
);
|
|
77
61
|
}
|
|
78
62
|
}
|
lib/screens/chapter_select_screen.dart
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import "package:flutter/material.dart";
|
|
2
|
-
import "package:only_bible_app/models.dart";
|
|
3
2
|
import "package:only_bible_app/navigation.dart";
|
|
4
3
|
import "package:only_bible_app/utils.dart";
|
|
5
4
|
import "package:only_bible_app/widgets/scaffold_menu.dart";
|
|
@@ -10,46 +9,33 @@ class ChapterSelectScreen extends StatelessWidget {
|
|
|
10
9
|
final String bibleName;
|
|
11
10
|
final int bookIndex;
|
|
12
11
|
|
|
13
|
-
const ChapterSelectScreen(
|
|
14
|
-
|
|
12
|
+
const ChapterSelectScreen({super.key, required this.bibleName, required this.bookIndex});
|
|
15
13
|
|
|
16
14
|
@override
|
|
17
15
|
Widget build(BuildContext context) {
|
|
18
|
-
return FutureBuilder(
|
|
19
|
-
future: loadBible(bibleName),
|
|
20
|
-
|
|
16
|
+
final bible = context.state.bible;
|
|
17
|
+
if (bible == null) {
|
|
21
|
-
|
|
18
|
+
return ColoredBox(
|
|
22
|
-
loading: () => ColoredBox(
|
|
23
|
-
|
|
19
|
+
color: Theme.of(context).colorScheme.surface,
|
|
24
|
-
|
|
20
|
+
child: const Center(child: CircularProgressIndicator()),
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
final book = bible.books[bookIndex];
|
|
24
|
+
return ScaffoldMenu(
|
|
25
|
+
child: CustomScrollView(
|
|
26
|
+
physics: const BouncingScrollPhysics(),
|
|
27
|
+
slivers: [
|
|
28
|
+
SliverHeading(title: context.bookNames[book.index], showClose: true),
|
|
29
|
+
SliverTileGrid(
|
|
30
|
+
children: List.generate(book.chapters.length, (index) {
|
|
31
|
+
return TextButton(
|
|
32
|
+
child: Text("${index + 1}"),
|
|
33
|
+
onPressed: () => replaceBookChapter(context, bible.name, bookIndex, index),
|
|
34
|
+
);
|
|
35
|
+
}),
|
|
25
36
|
),
|
|
26
|
-
success: (Bible? bible) {
|
|
27
|
-
final book = bible!.books[bookIndex];
|
|
28
|
-
return ScaffoldMenu(
|
|
29
|
-
child: CustomScrollView(
|
|
30
|
-
physics: const BouncingScrollPhysics(),
|
|
31
|
-
slivers: [
|
|
32
|
-
SliverHeading(
|
|
33
|
-
title: context.bookNames[book.index], showClose: true),
|
|
34
|
-
SliverTileGrid(
|
|
35
|
-
children: List.generate(book.chapters.length, (index) {
|
|
36
|
-
return TextButton(
|
|
37
|
-
child: Text("${index + 1}"),
|
|
38
|
-
onPressed: () => replaceBookChapter(
|
|
39
|
-
context, bible.name, bookIndex, index),
|
|
40
|
-
);
|
|
41
|
-
|
|
37
|
+
],
|
|
42
|
-
|
|
38
|
+
),
|
|
43
|
-
],
|
|
44
|
-
),
|
|
45
|
-
);
|
|
46
|
-
},
|
|
47
|
-
error: () => ColoredBox(
|
|
48
|
-
color: Theme.of(context).colorScheme.surface,
|
|
49
|
-
child: Center(child: Text("Could not load bible ${state.error}")),
|
|
50
|
-
),
|
|
51
|
-
);
|
|
52
|
-
},
|
|
53
39
|
);
|
|
54
40
|
}
|
|
55
41
|
}
|
lib/screens/chapter_view_screen.dart
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import "package:flutter/material.dart";
|
|
2
|
-
import "package:only_bible_app/models.dart";
|
|
3
|
-
import "package:only_bible_app/store/actions.dart";
|
|
4
2
|
import "package:only_bible_app/utils.dart";
|
|
5
3
|
import "package:only_bible_app/widgets/chapter_app_bar.dart";
|
|
6
4
|
import "package:only_bible_app/widgets/verses_view.dart";
|
|
@@ -19,42 +17,25 @@ class ChapterViewScreen extends StatelessWidget {
|
|
|
19
17
|
|
|
20
18
|
@override
|
|
21
19
|
Widget build(BuildContext context) {
|
|
22
|
-
return FutureBuilder(
|
|
23
|
-
future: loadBible(bibleName),
|
|
24
|
-
|
|
20
|
+
final bible = context.state.bible;
|
|
21
|
+
if (bible == null) {
|
|
25
|
-
|
|
22
|
+
return ColoredBox(
|
|
26
|
-
loading: () => ColoredBox(
|
|
27
|
-
|
|
23
|
+
color: Theme.of(context).colorScheme.surface,
|
|
28
|
-
child: const Center(
|
|
29
|
-
|
|
24
|
+
child: const Center(child: CircularProgressIndicator()),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
final book = bible.books[bookIndex];
|
|
28
|
+
final chapter = book.chapters[chapterIndex];
|
|
29
|
+
return Scaffold(
|
|
30
|
+
appBar: ChapterAppBar(bible: bible, book: book, chapter: chapter),
|
|
31
|
+
backgroundColor: Theme.of(context).colorScheme.surface,
|
|
32
|
+
body: SafeArea(
|
|
33
|
+
child: Column(
|
|
34
|
+
children: [
|
|
35
|
+
Expanded(child: VersesView(bible: bible, chapter: chapter)),
|
|
36
|
+
],
|
|
30
|
-
|
|
37
|
+
),
|
|
31
|
-
|
|
38
|
+
),
|
|
32
|
-
success: (Bible? bible) {
|
|
33
|
-
final book = bible!.books[bookIndex];
|
|
34
|
-
final chapter = book.chapters[chapterIndex];
|
|
35
|
-
return Scaffold(
|
|
36
|
-
appBar: ChapterAppBar(bible: bible, book: book, chapter: chapter),
|
|
37
|
-
backgroundColor: Theme.of(context).colorScheme.surface,
|
|
38
|
-
body: SafeArea(
|
|
39
|
-
child: Column(
|
|
40
|
-
children: [
|
|
41
|
-
Expanded(child: VersesView(bible: bible, chapter: chapter)),
|
|
42
|
-
],
|
|
43
|
-
),
|
|
44
|
-
),
|
|
45
|
-
);
|
|
46
|
-
},
|
|
47
|
-
error: () {
|
|
48
|
-
print(state.stackTrace);
|
|
49
|
-
return ColoredBox(
|
|
50
|
-
color: Theme.of(context).colorScheme.surface,
|
|
51
|
-
child: Center(
|
|
52
|
-
child: Text("Could not load the bible ${state.error}"),
|
|
53
|
-
),
|
|
54
|
-
);
|
|
55
|
-
},
|
|
56
|
-
);
|
|
57
|
-
},
|
|
58
39
|
);
|
|
59
40
|
}
|
|
60
41
|
}
|
lib/sheets/actions_sheet.dart
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import "package:flutter/material.dart";
|
|
2
2
|
import "package:only_bible_app/models.dart";
|
|
3
3
|
import "package:only_bible_app/navigation.dart";
|
|
4
|
-
import "package:only_bible_app/app.dart";
|
|
5
4
|
import "package:only_bible_app/store/actions.dart";
|
|
6
5
|
import "package:only_bible_app/theme.dart";
|
|
7
6
|
import "package:only_bible_app/utils.dart";
|
|
@@ -14,95 +13,75 @@ class ActionsSheet extends StatelessWidget {
|
|
|
14
13
|
|
|
15
14
|
@override
|
|
16
15
|
Widget build(BuildContext context) {
|
|
17
|
-
final iconColor =
|
|
16
|
+
final iconColor = context.state.darkMode ? Colors.white.withOpacity(0.9) : Colors.black.withOpacity(0.9);
|
|
18
|
-
? Colors.white.withOpacity(0.9)
|
|
19
|
-
: Colors.black.withOpacity(0.9);
|
|
20
|
-
final audioIcon = context.state.isPlaying
|
|
17
|
+
final audioIcon = context.state.isPlaying ? Icons.pause_circle_outline : Icons.play_circle_outline;
|
|
21
|
-
? Icons.pause_circle_outline
|
|
22
|
-
: Icons.play_circle_outline;
|
|
23
18
|
|
|
24
19
|
void onHighlight(int index) {
|
|
20
|
+
context.dispatch(
|
|
25
|
-
|
|
21
|
+
SetHighlightAction(
|
|
26
|
-
|
|
22
|
+
List<Verse>.from(context.state.selectedVerses),
|
|
27
|
-
|
|
23
|
+
index,
|
|
24
|
+
),
|
|
28
|
-
)
|
|
25
|
+
);
|
|
29
26
|
hideActions(context);
|
|
30
27
|
}
|
|
31
28
|
|
|
32
|
-
return
|
|
29
|
+
return Material(
|
|
33
|
-
|
|
30
|
+
elevation: 4,
|
|
31
|
+
borderRadius: BorderRadius.circular(28),
|
|
34
32
|
color: Theme.of(context).colorScheme.surface,
|
|
33
|
+
child: Container(
|
|
34
|
+
height: 50,
|
|
35
|
-
|
|
35
|
+
padding: const EdgeInsets.only(left: 8, right: 8),
|
|
36
|
-
|
|
36
|
+
child: Row(
|
|
37
|
-
|
|
37
|
+
mainAxisSize: MainAxisSize.min,
|
|
38
|
-
|
|
38
|
+
children: [
|
|
39
|
-
|
|
39
|
+
IconButton(
|
|
40
|
-
|
|
40
|
+
padding: EdgeInsets.zero,
|
|
41
|
-
|
|
41
|
+
onPressed: () {
|
|
42
|
-
|
|
42
|
+
context.dispatch(
|
|
43
|
-
|
|
43
|
+
RemoveHighlightAction(
|
|
44
|
-
|
|
44
|
+
List<Verse>.from(context.state.selectedVerses),
|
|
45
|
-
|
|
45
|
+
),
|
|
46
|
-
|
|
46
|
+
);
|
|
47
|
-
|
|
47
|
+
hideActions(context);
|
|
48
|
-
|
|
48
|
+
},
|
|
49
|
-
|
|
49
|
+
icon: Icon(Icons.cancel_outlined, size: 28, color: iconColor),
|
|
50
|
-
|
|
50
|
+
),
|
|
51
|
-
if (context.state.selectedVerses
|
|
52
|
-
.any((v) => context.state.getHighlight(v) != null)) ...[
|
|
53
51
|
HighlightButton(
|
|
54
52
|
index: 0,
|
|
55
|
-
color:
|
|
56
|
-
|
|
53
|
+
color: context.state.darkMode ? darkHighlights[0] : lightHighlights[0],
|
|
57
54
|
onHighlightSelected: onHighlight,
|
|
58
55
|
),
|
|
59
56
|
HighlightButton(
|
|
60
57
|
index: 1,
|
|
61
|
-
color:
|
|
62
|
-
|
|
58
|
+
color: context.state.darkMode ? darkHighlights[1] : lightHighlights[1],
|
|
63
59
|
onHighlightSelected: onHighlight,
|
|
64
60
|
),
|
|
65
61
|
HighlightButton(
|
|
66
62
|
index: 2,
|
|
67
|
-
color:
|
|
68
|
-
|
|
63
|
+
color: context.state.darkMode ? darkHighlights[2] : lightHighlights[2],
|
|
69
64
|
onHighlightSelected: onHighlight,
|
|
70
65
|
),
|
|
71
66
|
HighlightButton(
|
|
72
67
|
index: 3,
|
|
73
|
-
color:
|
|
74
|
-
|
|
68
|
+
color: context.state.darkMode ? darkHighlights[3] : lightHighlights[3],
|
|
75
69
|
onHighlightSelected: onHighlight,
|
|
76
70
|
),
|
|
77
|
-
] else ...[
|
|
78
|
-
IconButton(
|
|
79
|
-
padding: EdgeInsets.zero,
|
|
80
|
-
onPressed: () {
|
|
81
|
-
store.dispatch(
|
|
82
|
-
SetHighlightAction(
|
|
83
|
-
List<Verse>.from(store.state.selectedVerses),
|
|
84
|
-
0,
|
|
85
|
-
),
|
|
86
|
-
);
|
|
87
|
-
},
|
|
88
|
-
icon:
|
|
89
|
-
Icon(Icons.border_color_outlined, size: 28, color: iconColor),
|
|
90
|
-
),
|
|
91
71
|
IconButton(
|
|
92
72
|
padding: EdgeInsets.zero,
|
|
93
73
|
onPressed: () {
|
|
94
|
-
|
|
74
|
+
context.state.onPlay(context, bible);
|
|
95
75
|
},
|
|
96
76
|
icon: Icon(audioIcon, size: 34, color: iconColor),
|
|
97
77
|
),
|
|
98
78
|
IconButton(
|
|
99
79
|
padding: EdgeInsets.zero,
|
|
100
|
-
onPressed: () =>
|
|
101
|
-
|
|
80
|
+
onPressed: () => shareVerses(context, bible, context.state.selectedVerses),
|
|
102
81
|
icon: Icon(Icons.share_outlined, size: 34, color: iconColor),
|
|
103
82
|
),
|
|
104
83
|
],
|
|
105
|
-
|
|
84
|
+
),
|
|
106
85
|
),
|
|
107
86
|
);
|
|
108
87
|
}
|
lib/sheets/settings_sheet.dart
CHANGED
|
@@ -2,7 +2,6 @@ import "package:flutter/material.dart";
|
|
|
2
2
|
import "package:only_bible_app/models.dart";
|
|
3
3
|
import "package:only_bible_app/navigation.dart";
|
|
4
4
|
import "package:only_bible_app/store/actions.dart";
|
|
5
|
-
import "package:only_bible_app/app.dart";
|
|
6
5
|
import "package:only_bible_app/utils.dart";
|
|
7
6
|
import "package:settings_ui/settings_ui.dart";
|
|
8
7
|
|
|
@@ -31,26 +30,22 @@ class SettingsSheet extends StatelessWidget {
|
|
|
31
30
|
margin: const EdgeInsetsDirectional.symmetric(horizontal: 20),
|
|
32
31
|
tiles: [
|
|
33
32
|
SettingsTile.navigation(
|
|
34
|
-
leading:
|
|
35
|
-
|
|
33
|
+
leading: const Icon(Icons.book_outlined, color: Colors.blueAccent),
|
|
36
34
|
title: Text(context.l.bibleTitle),
|
|
37
35
|
value: Text(bible.name),
|
|
38
36
|
onPressed: changeBible,
|
|
39
37
|
),
|
|
40
38
|
SettingsTile.navigation(
|
|
41
|
-
leading:
|
|
42
|
-
|
|
39
|
+
leading: const Icon(Icons.color_lens_outlined, color: Colors.green),
|
|
43
40
|
title: Text(context.l.themeTitle),
|
|
44
41
|
trailing: ToggleButtons(
|
|
45
42
|
onPressed: (int index) {
|
|
46
|
-
|
|
43
|
+
context.dispatch(ToggleDarkModeAction());
|
|
47
44
|
},
|
|
48
45
|
highlightColor: Colors.transparent,
|
|
49
46
|
borderColor: Colors.grey,
|
|
50
47
|
borderRadius: const BorderRadius.all(Radius.circular(25)),
|
|
51
|
-
selectedColor:
|
|
48
|
+
selectedColor: context.state.darkMode ? Colors.lightBlue.shade300 : Colors.yellowAccent.shade700,
|
|
52
|
-
? Colors.lightBlue.shade300
|
|
53
|
-
: Colors.yellowAccent.shade700,
|
|
54
49
|
selectedBorderColor: Colors.grey,
|
|
55
50
|
color: Colors.grey,
|
|
56
51
|
fillColor: Colors.transparent,
|
|
@@ -58,7 +53,7 @@ class SettingsSheet extends StatelessWidget {
|
|
|
58
53
|
minHeight: 36.0,
|
|
59
54
|
minWidth: 50.0,
|
|
60
55
|
),
|
|
61
|
-
isSelected: [!
|
|
56
|
+
isSelected: [!context.state.darkMode, context.state.darkMode],
|
|
62
57
|
children: const [
|
|
63
58
|
Icon(Icons.light_mode),
|
|
64
59
|
Icon(Icons.dark_mode),
|
|
@@ -72,7 +67,7 @@ class SettingsSheet extends StatelessWidget {
|
|
|
72
67
|
color: context.theme.colorScheme.onSurface,
|
|
73
68
|
),
|
|
74
69
|
trailing: IconButton(
|
|
75
|
-
onPressed: () =>
|
|
70
|
+
onPressed: () => context.dispatch(UpdateTextScaleAction(0.1)),
|
|
76
71
|
icon: const Icon(
|
|
77
72
|
Icons.add_circle_outline,
|
|
78
73
|
size: 32,
|
|
@@ -87,7 +82,7 @@ class SettingsSheet extends StatelessWidget {
|
|
|
87
82
|
color: context.theme.colorScheme.onSurface,
|
|
88
83
|
),
|
|
89
84
|
trailing: IconButton(
|
|
90
|
-
onPressed: () =>
|
|
85
|
+
onPressed: () => context.dispatch(UpdateTextScaleAction(-0.1)),
|
|
91
86
|
icon: const Icon(
|
|
92
87
|
Icons.remove_circle_outline,
|
|
93
88
|
size: 32,
|
|
@@ -102,14 +97,13 @@ class SettingsSheet extends StatelessWidget {
|
|
|
102
97
|
color: context.theme.colorScheme.onSurface,
|
|
103
98
|
),
|
|
104
99
|
title: Text(context.l.boldFontTitle),
|
|
105
|
-
onToggle: (value) =>
|
|
100
|
+
onToggle: (value) => context.dispatch(ToggleBoldFontAction()),
|
|
106
101
|
),
|
|
107
102
|
SettingsTile.switchTile(
|
|
108
103
|
initialValue: context.state.engTitles,
|
|
109
|
-
leading:
|
|
110
|
-
|
|
104
|
+
leading: Icon(Icons.abc, color: context.theme.colorScheme.onSurface),
|
|
111
105
|
title: Text(context.l.engTitles),
|
|
112
|
-
onToggle: (value) =>
|
|
106
|
+
onToggle: (value) => context.dispatch(ToggleEngTitlesAction()),
|
|
113
107
|
),
|
|
114
108
|
],
|
|
115
109
|
),
|
|
@@ -137,8 +131,7 @@ class SettingsSheet extends StatelessWidget {
|
|
|
137
131
|
onPressed: showTermsAndConditions,
|
|
138
132
|
),
|
|
139
133
|
SettingsTile.navigation(
|
|
140
|
-
leading:
|
|
141
|
-
|
|
134
|
+
leading: const Icon(Icons.share_outlined, color: Colors.blueAccent),
|
|
142
135
|
title: Text(context.l.shareAppTitle),
|
|
143
136
|
onPressed: shareAppLink,
|
|
144
137
|
),
|
lib/store/actions.dart
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import "package:async_redux/async_redux.dart";
|
|
2
2
|
import "package:only_bible_app/models.dart";
|
|
3
3
|
import "package:only_bible_app/store/app_state.dart";
|
|
4
|
+
import "package:only_bible_app/utils.dart";
|
|
5
|
+
|
|
6
|
+
class LoadBibleAction extends ReduxAction<AppState> {
|
|
7
|
+
@override
|
|
8
|
+
Future<AppState> reduce() async {
|
|
9
|
+
if (state.bible != null && state.bible!.name == state.bibleName) {
|
|
10
|
+
return state;
|
|
11
|
+
}
|
|
12
|
+
final bible = await loadBible(state.bibleName);
|
|
13
|
+
return state.copy(bible: bible);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
4
16
|
|
|
5
17
|
class FirstOpenDoneAction extends ReduxAction<AppState> {
|
|
6
18
|
@override
|
|
@@ -68,16 +80,11 @@ class SelectVerseAction extends ReduxAction<AppState> {
|
|
|
68
80
|
@override
|
|
69
81
|
AppState reduce() {
|
|
70
82
|
final isSelected = state.selectedVerses.any(
|
|
71
|
-
(el) =>
|
|
72
|
-
el.book == verse.book &&
|
|
73
|
-
|
|
83
|
+
(el) => el.book == verse.book && el.chapter == verse.chapter && el.index == verse.index,
|
|
74
|
-
el.index == verse.index,
|
|
75
84
|
);
|
|
76
85
|
if (isSelected) {
|
|
77
86
|
return state.copy(
|
|
78
|
-
selectedVerses: state.selectedVerses
|
|
79
|
-
|
|
87
|
+
selectedVerses: state.selectedVerses.where((it) => it.index != verse.index).toList(),
|
|
80
|
-
.toList(),
|
|
81
88
|
);
|
|
82
89
|
} else {
|
|
83
90
|
return state.copy(
|
lib/store/app_state.dart
CHANGED
|
@@ -4,7 +4,6 @@ import "package:just_audio/just_audio.dart";
|
|
|
4
4
|
import "package:only_bible_app/dialog.dart";
|
|
5
5
|
import "package:only_bible_app/models.dart";
|
|
6
6
|
import "package:only_bible_app/store/actions.dart";
|
|
7
|
-
import "package:only_bible_app/app.dart";
|
|
8
7
|
import "package:only_bible_app/store/buffer_audio_source.dart";
|
|
9
8
|
import "package:only_bible_app/theme.dart";
|
|
10
9
|
import "package:only_bible_app/utils.dart";
|
|
@@ -25,6 +24,7 @@ class AppState {
|
|
|
25
24
|
final bool isPlaying;
|
|
26
25
|
final List<Verse> selectedVerses;
|
|
27
26
|
final Map<String, int> highlights;
|
|
27
|
+
final Bible? bible;
|
|
28
28
|
|
|
29
29
|
const AppState({
|
|
30
30
|
this.firstOpen = true,
|
|
@@ -39,6 +39,7 @@ class AppState {
|
|
|
39
39
|
this.isPlaying = false,
|
|
40
40
|
this.selectedVerses = const [],
|
|
41
41
|
this.highlights = const {},
|
|
42
|
+
this.bible,
|
|
42
43
|
});
|
|
43
44
|
|
|
44
45
|
AppState copy({
|
|
@@ -54,6 +55,7 @@ class AppState {
|
|
|
54
55
|
bool? isPlaying,
|
|
55
56
|
List<Verse>? selectedVerses,
|
|
56
57
|
Map<String, int>? highlights,
|
|
58
|
+
Bible? bible,
|
|
57
59
|
}) {
|
|
58
60
|
return AppState(
|
|
59
61
|
firstOpen: firstOpen ?? this.firstOpen,
|
|
@@ -68,6 +70,7 @@ class AppState {
|
|
|
68
70
|
isPlaying: isPlaying ?? this.isPlaying,
|
|
69
71
|
selectedVerses: selectedVerses ?? this.selectedVerses,
|
|
70
72
|
highlights: highlights ?? this.highlights,
|
|
73
|
+
bible: bible ?? this.bible,
|
|
71
74
|
);
|
|
72
75
|
}
|
|
73
76
|
|
|
@@ -94,9 +97,7 @@ class AppState {
|
|
|
94
97
|
textScale: (json["textScale"] as num?)?.toDouble() ?? 0.0,
|
|
95
98
|
savedBook: json["savedBook"] as int? ?? 0,
|
|
96
99
|
savedChapter: json["savedChapter"] as int? ?? 0,
|
|
97
|
-
highlights: (json["highlights"] as Map<String, dynamic>?)
|
|
100
|
+
highlights: (json["highlights"] as Map<String, dynamic>?)?.map((k, v) => MapEntry(k, v as int)) ?? const {},
|
|
98
|
-
?.map((k, v) => MapEntry(k, v as int)) ??
|
|
99
|
-
const {},
|
|
100
101
|
);
|
|
101
102
|
|
|
102
103
|
Color? getHighlight(Verse v) {
|
|
@@ -108,15 +109,13 @@ class AppState {
|
|
|
108
109
|
|
|
109
110
|
bool isVerseSelected(Verse v) {
|
|
110
111
|
return selectedVerses.any(
|
|
111
|
-
(el) =>
|
|
112
|
-
|
|
112
|
+
(el) => el.book == v.book && el.chapter == v.chapter && el.index == v.index,
|
|
113
113
|
);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
TextStyle getHighlightStyle(BuildContext context, Verse v, bool heading) {
|
|
117
117
|
final isSelected = selectedVerses.any(
|
|
118
|
-
(el) =>
|
|
119
|
-
|
|
118
|
+
(el) => el.book == v.book && el.chapter == v.chapter && el.index == v.index,
|
|
120
119
|
);
|
|
121
120
|
|
|
122
121
|
if (!heading && isSelected) {
|
|
@@ -128,9 +127,7 @@ class AppState {
|
|
|
128
127
|
if (darkMode) {
|
|
129
128
|
return TextStyle(
|
|
130
129
|
backgroundColor: highlight?.withValues(alpha: 0.7),
|
|
131
|
-
color: highlight != null
|
|
132
|
-
? Colors.white
|
|
133
|
-
|
|
130
|
+
color: highlight != null ? Colors.white : Theme.of(context).colorScheme.onSurface,
|
|
134
131
|
);
|
|
135
132
|
}
|
|
136
133
|
return TextStyle(
|
|
@@ -139,17 +136,16 @@ class AppState {
|
|
|
139
136
|
}
|
|
140
137
|
|
|
141
138
|
Future<void> onPlay(BuildContext context, Bible bible) async {
|
|
142
|
-
final versesToPlay = List<Verse>.from(
|
|
139
|
+
final versesToPlay = List<Verse>.from(context.state.selectedVerses);
|
|
143
|
-
if (
|
|
140
|
+
if (context.state.isPlaying) {
|
|
144
141
|
await player.pause();
|
|
145
|
-
|
|
142
|
+
context.dispatch(SetPlayingAction(false));
|
|
146
143
|
} else {
|
|
147
|
-
|
|
144
|
+
context.dispatch(SetPlayingAction(true));
|
|
148
145
|
for (final v in versesToPlay) {
|
|
149
146
|
final pathname = "${bible.name}_${v.book}_${v.chapter}_${v.index}";
|
|
150
147
|
try {
|
|
151
|
-
final data =
|
|
152
|
-
|
|
148
|
+
final data = await convertText(context.currentLang.audioVoice, v.text);
|
|
153
149
|
await player.setAudioSource(BufferAudioSource(data));
|
|
154
150
|
await player.play();
|
|
155
151
|
await player.stop();
|
|
@@ -166,7 +162,7 @@ class AppState {
|
|
|
166
162
|
return;
|
|
167
163
|
} finally {
|
|
168
164
|
await player.pause();
|
|
169
|
-
|
|
165
|
+
context.dispatch(SetPlayingAction(false));
|
|
170
166
|
}
|
|
171
167
|
}
|
|
172
168
|
}
|
lib/theme.dart
CHANGED
|
@@ -162,7 +162,7 @@ final lightTheme = ThemeData(
|
|
|
162
162
|
),
|
|
163
163
|
headlineMedium: TextStyle(
|
|
164
164
|
color: lightColorScheme.onSurface,
|
|
165
|
-
fontSize:
|
|
165
|
+
fontSize: 24,
|
|
166
166
|
fontWeight: FontWeight.w500,
|
|
167
167
|
letterSpacing: 0.5,
|
|
168
168
|
),
|
|
@@ -172,6 +172,12 @@ final lightTheme = ThemeData(
|
|
|
172
172
|
color: lightColorScheme.primary,
|
|
173
173
|
letterSpacing: 0,
|
|
174
174
|
),
|
|
175
|
+
labelLarge: TextStyle(
|
|
176
|
+
fontSize: 16,
|
|
177
|
+
fontWeight: FontWeight.w700,
|
|
178
|
+
color: lightColorScheme.onSurface,
|
|
179
|
+
letterSpacing: 0,
|
|
180
|
+
),
|
|
175
181
|
),
|
|
176
182
|
);
|
|
177
183
|
|
lib/utils.dart
CHANGED
|
@@ -2,22 +2,19 @@ import "dart:convert";
|
|
|
2
2
|
import "package:async_redux/async_redux.dart";
|
|
3
3
|
import "package:http/http.dart" as http;
|
|
4
4
|
import "package:flutter/services.dart";
|
|
5
|
-
import "package:only_bible_app/dialog.dart";
|
|
6
|
-
import "package:only_bible_app/env.dart";
|
|
7
|
-
import "package:only_bible_app/models.dart";
|
|
8
|
-
import "package:only_bible_app/store/app_state.dart";
|
|
9
|
-
|
|
10
5
|
import "package:package_info_plus/package_info_plus.dart";
|
|
11
6
|
import "package:url_launcher/url_launcher.dart";
|
|
12
|
-
import "package:flutter/foundation.dart"
|
|
7
|
+
import "package:flutter/foundation.dart" show TargetPlatform, defaultTargetPlatform, kDebugMode;
|
|
13
|
-
show TargetPlatform, defaultTargetPlatform, kDebugMode;
|
|
14
8
|
import "package:flutter/material.dart";
|
|
15
9
|
import "package:only_bible_app/l10n/app_localizations.dart";
|
|
16
10
|
import "package:flutter_azure_tts/flutter_azure_tts.dart";
|
|
11
|
+
import "package:only_bible_app/dialog.dart";
|
|
12
|
+
import "package:only_bible_app/env.dart";
|
|
13
|
+
import "package:only_bible_app/models.dart";
|
|
14
|
+
import "package:only_bible_app/store/app_state.dart";
|
|
17
15
|
|
|
18
16
|
extension IterableUtils<E> on Iterable<E> {
|
|
19
|
-
Iterable<E> sortedBy(Comparable Function(E e) key) =>
|
|
17
|
+
Iterable<E> sortedBy(Comparable Function(E e) key) => toList()..sort((a, b) => key(a).compareTo(key(b)));
|
|
20
|
-
toList()..sort((a, b) => key(a).compareTo(key(b)));
|
|
21
18
|
|
|
22
19
|
Iterable<E> removeBy(bool Function(E e) key) => toList()..removeWhere(key);
|
|
23
20
|
|
|
@@ -45,12 +42,15 @@ extension AppContext on BuildContext {
|
|
|
45
42
|
|
|
46
43
|
AppState get state => StoreProvider.state<AppState>(this);
|
|
47
44
|
|
|
45
|
+
void dispatch(ReduxAction<AppState> action) => StoreProvider.dispatch<AppState>(this, action);
|
|
46
|
+
|
|
47
|
+
Future<void> dispatchAndWait(ReduxAction<AppState> action) => StoreProvider.dispatchAndWait<AppState>(this, action);
|
|
48
|
+
|
|
48
49
|
AppLocalizations get l => state.engTitles && state.languageCode != "en"
|
|
49
50
|
? lookupAppLocalizations(const Locale("en"))
|
|
50
51
|
: AppLocalizations.of(this)!;
|
|
51
52
|
|
|
52
|
-
AppLocalizations get currentLang => supportedLocalizations
|
|
53
|
-
|
|
53
|
+
AppLocalizations get currentLang => supportedLocalizations.firstWhere((el) => el.languageCode == state.languageCode);
|
|
54
54
|
|
|
55
55
|
double get actionsHeight {
|
|
56
56
|
if (isIOS()) {
|
|
@@ -183,8 +183,7 @@ Future<Uint8List> convertText(String langCode, String text) async {
|
|
|
183
183
|
|
|
184
184
|
Future<Bible> loadBible(String name) async {
|
|
185
185
|
final bytes = await rootBundle.load("assets/bibles/$name.txt");
|
|
186
|
-
final books = parseBible(
|
|
187
|
-
|
|
186
|
+
final books = parseBible(name, utf8.decode(bytes.buffer.asUint8List(), allowMalformed: false));
|
|
188
187
|
// await Future.delayed(Duration(seconds: 2));
|
|
189
188
|
return Bible(
|
|
190
189
|
name: name,
|
lib/widgets/sliver_heading.dart
CHANGED
|
@@ -25,8 +25,10 @@ class SliverHeading extends StatelessWidget {
|
|
|
25
25
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
26
26
|
children: [
|
|
27
27
|
Expanded(
|
|
28
|
-
child: Text(
|
|
28
|
+
child: Text(
|
|
29
|
+
title,
|
|
29
|
-
|
|
30
|
+
style: Theme.of(context).textTheme.headlineMedium,
|
|
31
|
+
),
|
|
30
32
|
),
|
|
31
33
|
if (showClose)
|
|
32
34
|
IconButton(
|
lib/widgets/verses_view.dart
CHANGED
|
@@ -3,7 +3,6 @@ import "package:flutter/material.dart";
|
|
|
3
3
|
import "package:flutter_swipe_detector/flutter_swipe_detector.dart";
|
|
4
4
|
import "package:only_bible_app/models.dart";
|
|
5
5
|
import "package:only_bible_app/navigation.dart";
|
|
6
|
-
import "package:only_bible_app/app.dart";
|
|
7
6
|
import "package:only_bible_app/store/actions.dart";
|
|
8
7
|
import "package:only_bible_app/utils.dart";
|
|
9
8
|
|
|
@@ -55,40 +54,28 @@ class VersesView extends StatelessWidget {
|
|
|
55
54
|
(v) => [
|
|
56
55
|
if (v.heading != "")
|
|
57
56
|
TextSpan(
|
|
58
|
-
text:
|
|
59
|
-
|
|
57
|
+
text: "${v.heading.replaceAll("<br>", "\n")}\n",
|
|
60
|
-
style: context.
|
|
58
|
+
style: Theme.of(context).textTheme.labelLarge,
|
|
61
|
-
.getHighlightStyle(context, v, true)
|
|
62
|
-
.copyWith(
|
|
63
|
-
fontSize: 18,
|
|
64
|
-
fontWeight: FontWeight.w600,
|
|
65
|
-
height: 2,
|
|
66
|
-
),
|
|
67
59
|
),
|
|
68
60
|
WidgetSpan(
|
|
69
61
|
child: Transform.translate(
|
|
70
62
|
offset: const Offset(0, -2),
|
|
71
63
|
child: Text(
|
|
72
64
|
"${v.index + 1} ",
|
|
73
|
-
style: Theme.of(context)
|
|
65
|
+
style: Theme.of(context).textTheme.labelMedium,
|
|
74
|
-
.textTheme
|
|
75
|
-
.labelMedium,
|
|
76
66
|
),
|
|
77
67
|
),
|
|
78
68
|
),
|
|
79
69
|
TextSpan(
|
|
80
70
|
text: "${v.text}\n",
|
|
81
|
-
style: context.state
|
|
82
|
-
|
|
71
|
+
style: context.state.getHighlightStyle(context, v, false),
|
|
83
72
|
recognizer: TapGestureRecognizer()
|
|
84
73
|
..onTap = () {
|
|
85
|
-
|
|
74
|
+
context.dispatch(SelectVerseAction(v));
|
|
86
|
-
if (store
|
|
87
|
-
|
|
75
|
+
if (context.state.selectedVerses.isNotEmpty) {
|
|
88
76
|
showActions(context, bible);
|
|
89
77
|
}
|
|
90
|
-
if (store
|
|
91
|
-
|
|
78
|
+
if (context.state.selectedVerses.isEmpty) {
|
|
92
79
|
hideActions(context);
|
|
93
80
|
}
|
|
94
81
|
},
|
|
@@ -110,8 +97,7 @@ class VersesView extends StatelessWidget {
|
|
|
110
97
|
),
|
|
111
98
|
Padding(
|
|
112
99
|
padding: EdgeInsets.only(
|
|
113
|
-
bottom:
|
|
114
|
-
|
|
100
|
+
bottom: context.state.selectedVerses.isNotEmpty ? 120 : 0,
|
|
115
101
|
),
|
|
116
102
|
),
|
|
117
103
|
],
|