~repos /only-bible-app
git clone https://pyrossh.dev/repos/only-bible-app.git
The only bible app you will ever need. No ads. No in-app purchases. No distractions.
c322445f
—
pyrossh 2 years ago
add loader
- lib/atom.dart +8 -0
- lib/main.dart +1 -1
- lib/models.dart +1 -1
- lib/navigation.dart +35 -2
- lib/screens/chapter_view_screen.dart +35 -37
- lib/sheets/actions_sheet.dart +1 -1
- lib/state.dart +34 -15
- lib/utils.dart +12 -58
- lib/widgets/bible_loader.dart +27 -0
- lib/widgets/scaffold_markdown.dart +1 -1
- pubspec.lock +0 -16
- pubspec.yaml +0 -1
lib/atom.dart
CHANGED
|
@@ -9,6 +9,14 @@ ensureAtomsInitialized(GetStorage box) async {
|
|
|
9
9
|
await localBox.initStorage;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
class Cubit<T> extends ValueNotifier<T> {
|
|
13
|
+
Cubit(T initialState) : super(initialState);
|
|
14
|
+
|
|
15
|
+
void emit(T state) {
|
|
16
|
+
super.value = state;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
class Atom<T> extends ValueNotifier<T> {
|
|
13
21
|
final String key;
|
|
14
22
|
final bool persist;
|
lib/main.dart
CHANGED
|
@@ -26,7 +26,7 @@ void main() async {
|
|
|
26
26
|
usePathUrlStrategy();
|
|
27
27
|
await initState();
|
|
28
28
|
updateStatusBar(darkMode.value);
|
|
29
|
-
|
|
29
|
+
bibleCache.value = loadBible(bibleName.value);
|
|
30
30
|
runApp(const App());
|
|
31
31
|
FlutterNativeSplash.remove();
|
|
32
32
|
}
|
lib/models.dart
CHANGED
|
@@ -116,4 +116,4 @@ class HistoryFrame {
|
|
|
116
116
|
final int chapter;
|
|
117
117
|
|
|
118
118
|
const HistoryFrame({required this.book, required this.chapter});
|
|
119
|
-
}
|
|
119
|
+
}
|
lib/navigation.dart
CHANGED
|
@@ -31,6 +31,39 @@ final Atom<bool> highlightsShown = Atom<bool>(
|
|
|
31
31
|
},
|
|
32
32
|
);
|
|
33
33
|
|
|
34
|
+
createNoTransitionPageRoute(Widget page) {
|
|
35
|
+
return PageRouteBuilder(
|
|
36
|
+
opaque: false,
|
|
37
|
+
transitionDuration: Duration.zero,
|
|
38
|
+
reverseTransitionDuration: Duration.zero,
|
|
39
|
+
pageBuilder: (context, _, __) => page,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
createSlideRoute({required BuildContext context, TextDirection? slideDir, required Widget page}) {
|
|
44
|
+
if (context.isWide || slideDir == null) {
|
|
45
|
+
return PageRouteBuilder(
|
|
46
|
+
pageBuilder: (context, _, __) {
|
|
47
|
+
return page;
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return PageRouteBuilder(
|
|
52
|
+
pageBuilder: (context, animation, secondaryAnimation) => page,
|
|
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
|
+
var 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
|
+
|
|
34
67
|
updateStatusBar(bool v) {
|
|
35
68
|
if (v) {
|
|
36
69
|
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
|
@@ -162,9 +195,9 @@ shareAppLink(BuildContext context) {
|
|
|
162
195
|
|
|
163
196
|
rateApp(BuildContext context) {
|
|
164
197
|
if (isAndroid()) {
|
|
165
|
-
openUrl(
|
|
198
|
+
context.openUrl("https://play.google.com/store/apps/details?id=packageName");
|
|
166
199
|
} else if (isIOS()) {
|
|
167
|
-
openUrl(
|
|
200
|
+
context.openUrl("https://apps.apple.com/us/app/only-bible-app/packageName");
|
|
168
201
|
}
|
|
169
202
|
}
|
|
170
203
|
|
lib/screens/chapter_view_screen.dart
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import "package:flutter/material.dart";
|
|
2
|
-
import "package:only_bible_app/state.dart";
|
|
3
2
|
import "package:only_bible_app/utils.dart";
|
|
3
|
+
import "package:only_bible_app/widgets/bible_loader.dart";
|
|
4
4
|
import "package:only_bible_app/widgets/chapter_app_bar.dart";
|
|
5
5
|
import "package:only_bible_app/widgets/sidebar.dart";
|
|
6
6
|
import "package:only_bible_app/widgets/verses_view.dart";
|
|
7
|
-
import "package:provider/provider.dart";
|
|
8
7
|
|
|
9
8
|
class ChapterViewScreen extends StatelessWidget {
|
|
10
9
|
final int bookIndex;
|
|
@@ -14,35 +13,19 @@ class ChapterViewScreen extends StatelessWidget {
|
|
|
14
13
|
|
|
15
14
|
@override
|
|
16
15
|
Widget build(BuildContext context) {
|
|
17
|
-
// FutureBuilder(
|
|
18
|
-
// future: loadData(), // This reloads everytime theme changes
|
|
19
|
-
|
|
16
|
+
if (context.isWide) {
|
|
20
|
-
// if (snapshot.hasData && snapshot.data != null && snapshot.connectionState == ConnectionState.done) {
|
|
21
|
-
// return ChangeNotifierProvider(
|
|
22
|
-
// create: (_) => BibleViewModel(bible: snapshot.data!.$1),
|
|
23
|
-
// child: ChapterViewScreen(book: snapshot.data!.$2, chapter: snapshot.data!.$3),
|
|
24
|
-
// );
|
|
25
|
-
|
|
17
|
+
return Scaffold(
|
|
26
|
-
// return ColoredBox(
|
|
27
|
-
|
|
18
|
+
backgroundColor: Theme.of(context).colorScheme.background,
|
|
28
|
-
// child: const Center(
|
|
29
|
-
// child: CircularProgressIndicator(),
|
|
30
|
-
// ),
|
|
31
|
-
// );
|
|
32
|
-
|
|
19
|
+
body: SafeArea(
|
|
20
|
+
child: Row(
|
|
21
|
+
children: [
|
|
33
|
-
|
|
22
|
+
const Sidebar(),
|
|
23
|
+
Flexible(
|
|
24
|
+
child: BibleLoader(
|
|
25
|
+
builder: (bible) {
|
|
34
|
-
|
|
26
|
+
final book = bible.books[bookIndex];
|
|
35
|
-
|
|
27
|
+
final chapter = book.chapters[chapterIndex];
|
|
36
|
-
return Scaffold(
|
|
37
|
-
appBar: context.isWide ? null : ChapterAppBar(book: book, chapter: chapter),
|
|
38
|
-
backgroundColor: Theme.of(context).colorScheme.background,
|
|
39
|
-
body: SafeArea(
|
|
40
|
-
child: context.isWide
|
|
41
|
-
? Row(
|
|
42
|
-
children: [
|
|
43
|
-
const Sidebar(),
|
|
44
|
-
Flexible(
|
|
45
|
-
|
|
28
|
+
return Column(
|
|
46
29
|
children: [
|
|
47
30
|
ChapterAppBar(book: book, chapter: chapter),
|
|
48
31
|
const Padding(
|
|
@@ -53,12 +36,27 @@ class ChapterViewScreen extends StatelessWidget {
|
|
|
53
36
|
child: VersesView(chapter: chapter),
|
|
54
37
|
),
|
|
55
38
|
],
|
|
39
|
+
);
|
|
40
|
+
},
|
|
56
|
-
|
|
41
|
+
),
|
|
57
|
-
|
|
42
|
+
),
|
|
58
|
-
|
|
43
|
+
],
|
|
59
|
-
)
|
|
60
|
-
: VersesView(chapter: chapter),
|
|
61
|
-
|
|
44
|
+
),
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return BibleLoader(
|
|
49
|
+
builder: (bible) {
|
|
50
|
+
final book = bible.books[bookIndex];
|
|
51
|
+
final chapter = book.chapters[chapterIndex];
|
|
52
|
+
return Scaffold(
|
|
53
|
+
appBar: ChapterAppBar(book: book, chapter: chapter),
|
|
54
|
+
backgroundColor: Theme.of(context).colorScheme.background,
|
|
55
|
+
body: SafeArea(
|
|
56
|
+
child: VersesView(chapter: chapter),
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
},
|
|
62
60
|
);
|
|
63
61
|
}
|
|
64
62
|
}
|
lib/sheets/actions_sheet.dart
CHANGED
|
@@ -36,7 +36,7 @@ class ActionsSheet extends StatelessWidget {
|
|
|
36
36
|
if (audioEnabled) {
|
|
37
37
|
onPlay(context);
|
|
38
38
|
} else {
|
|
39
|
-
showError(context, context.
|
|
39
|
+
showError(context, context.l.audioNotAvailable);
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
42
|
icon: Icon(audioIcon, size: 34, color: audioEnabled ? iconColor : Colors.grey),
|
lib/state.dart
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import "dart:convert";
|
|
1
2
|
import "dart:developer";
|
|
2
3
|
import "package:firebase_crashlytics/firebase_crashlytics.dart";
|
|
3
4
|
import "package:firebase_storage/firebase_storage.dart";
|
|
4
5
|
import "package:flutter/material.dart";
|
|
6
|
+
import "package:flutter/services.dart";
|
|
5
7
|
import "package:get_storage/get_storage.dart";
|
|
6
8
|
import "package:just_audio/just_audio.dart";
|
|
7
9
|
import "package:only_bible_app/atom.dart";
|
|
@@ -53,27 +55,44 @@ final Atom<Bible> bible = Atom<Bible>(
|
|
|
53
55
|
},
|
|
54
56
|
);
|
|
55
57
|
|
|
58
|
+
final bibleCache = Atom<Future<Bible?>?>(
|
|
59
|
+
key: "bible",
|
|
60
|
+
persist: false,
|
|
61
|
+
initialValue: null,
|
|
62
|
+
);
|
|
63
|
+
|
|
56
64
|
updateCurrentBible(BuildContext context, Locale l, String name) async {
|
|
57
65
|
languageCode.value = l.languageCode;
|
|
58
|
-
bibleName.
|
|
66
|
+
bibleName.update!(name);
|
|
59
|
-
|
|
67
|
+
bibleCache.value = loadBible(name);
|
|
60
68
|
}
|
|
61
69
|
|
|
62
|
-
loadBible() async {
|
|
63
|
-
// Trace customTrace;
|
|
64
|
-
// if (!isDesktop()) {
|
|
65
|
-
// customTrace = FirebasePerformance.instance.newTrace("loadBible");
|
|
66
|
-
// await customTrace.start();
|
|
67
|
-
// }
|
|
68
|
-
|
|
70
|
+
Future<Bible> getBibleFromAsset(String file) async {
|
|
69
|
-
|
|
71
|
+
final bytes = await rootBundle.load("assets/bibles/$file.txt");
|
|
72
|
+
final books = getBibleFromText(utf8.decode(bytes.buffer.asUint8List(), allowMalformed: false));
|
|
70
|
-
//
|
|
73
|
+
// await Future.delayed(Duration(seconds: 1));
|
|
71
|
-
// }
|
|
72
|
-
|
|
74
|
+
return Bible.withBooks(
|
|
73
75
|
name: bibleName.value,
|
|
74
76
|
books: books,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
Future<Bible?> loadBible(String name) async {
|
|
81
|
+
print("loadBible ${name}");
|
|
82
|
+
return getBibleFromAsset(name).then((value) {
|
|
83
|
+
bible.update!(value);
|
|
84
|
+
return value;
|
|
75
|
-
)
|
|
85
|
+
});
|
|
76
86
|
}
|
|
87
|
+
// Trace customTrace;
|
|
88
|
+
// if (!isDesktop()) {
|
|
89
|
+
// customTrace = FirebasePerformance.instance.newTrace("loadBible");
|
|
90
|
+
// await customTrace.start();
|
|
91
|
+
// }
|
|
92
|
+
// bibleLoading = Future.delayed(const Duration(seconds: 2)).then((value) => getBibleFromAsset(bibleName.value));
|
|
93
|
+
// if (!isDesktop()) {
|
|
94
|
+
// await customTrace.stop();
|
|
95
|
+
// }
|
|
77
96
|
|
|
78
97
|
final Atom<bool> engTitles = Atom<bool>(
|
|
79
98
|
key: "engTitles",
|
|
@@ -251,7 +270,7 @@ onPlay(BuildContext context) async {
|
|
|
251
270
|
log("Could not play audio", name: "play", error: (err.toString(), pathname));
|
|
252
271
|
FirebaseCrashlytics.instance.recordFlutterError(FlutterErrorDetails(exception: (err.toString(), pathname)));
|
|
253
272
|
if (context.mounted) {
|
|
254
|
-
showError(context, context.
|
|
273
|
+
showError(context, context.l.audioError);
|
|
255
274
|
}
|
|
256
275
|
return;
|
|
257
276
|
} finally {
|
lib/utils.dart
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import "dart:convert";
|
|
2
1
|
import "package:only_bible_app/dialog.dart";
|
|
3
2
|
import "package:only_bible_app/state.dart";
|
|
4
3
|
import "package:url_launcher/url_launcher.dart";
|
|
5
4
|
import "package:flutter/foundation.dart" show defaultTargetPlatform, TargetPlatform;
|
|
6
5
|
import "package:flutter/material.dart";
|
|
7
|
-
import "package:flutter/services.dart";
|
|
8
|
-
import "package:only_bible_app/models.dart";
|
|
9
6
|
import "package:flutter_gen/gen_l10n/app_localizations.dart";
|
|
10
|
-
import "package:provider/provider.dart";
|
|
11
7
|
|
|
12
8
|
extension MyIterable<E> on Iterable<E> {
|
|
13
9
|
Iterable<E> sortedBy(Comparable Function(E e) key) => toList()..sort((a, b) => key(a).compareTo(key(b)));
|
|
@@ -20,11 +16,7 @@ extension MyIterable<E> on Iterable<E> {
|
|
|
20
16
|
extension AppContext on BuildContext {
|
|
21
17
|
ThemeData get theme => Theme.of(this);
|
|
22
18
|
|
|
23
|
-
AppLocalizations get l => engTitles.
|
|
19
|
+
AppLocalizations get l => engTitles.watch(this) && languageCode.watch(this) != "en"
|
|
24
|
-
? lookupAppLocalizations(const Locale("en"))
|
|
25
|
-
: AppLocalizations.of(this)!;
|
|
26
|
-
|
|
27
|
-
AppLocalizations get lEvent => engTitles.value && languageCode.value != "en"
|
|
28
20
|
? lookupAppLocalizations(const Locale("en"))
|
|
29
21
|
: AppLocalizations.of(this)!;
|
|
30
22
|
|
|
@@ -125,6 +117,17 @@ extension AppContext on BuildContext {
|
|
|
125
117
|
l.revelation,
|
|
126
118
|
];
|
|
127
119
|
}
|
|
120
|
+
|
|
121
|
+
openUrl(String url) async {
|
|
122
|
+
final uri = Uri.parse(url);
|
|
123
|
+
if (await canLaunchUrl(uri)) {
|
|
124
|
+
if (await launchUrl(uri)) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (!mounted) return;
|
|
129
|
+
showError(this, l.urlError);
|
|
130
|
+
}
|
|
128
131
|
}
|
|
129
132
|
|
|
130
133
|
bool isDesktop() {
|
|
@@ -140,52 +143,3 @@ bool isIOS() {
|
|
|
140
143
|
bool isAndroid() {
|
|
141
144
|
return defaultTargetPlatform == TargetPlatform.android;
|
|
142
145
|
}
|
|
143
|
-
|
|
144
|
-
createNoTransitionPageRoute(Widget page) {
|
|
145
|
-
return PageRouteBuilder(
|
|
146
|
-
opaque: false,
|
|
147
|
-
transitionDuration: Duration.zero,
|
|
148
|
-
reverseTransitionDuration: Duration.zero,
|
|
149
|
-
pageBuilder: (context, _, __) => page,
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
createSlideRoute({required BuildContext context, TextDirection? slideDir, required Widget page}) {
|
|
154
|
-
if (context.isWide || slideDir == null) {
|
|
155
|
-
return PageRouteBuilder(
|
|
156
|
-
pageBuilder: (context, _, __) {
|
|
157
|
-
return page;
|
|
158
|
-
},
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
return PageRouteBuilder(
|
|
162
|
-
pageBuilder: (context, animation, secondaryAnimation) => page,
|
|
163
|
-
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
|
164
|
-
const begin = Offset(1.0, 0.0);
|
|
165
|
-
const end = Offset.zero;
|
|
166
|
-
const curve = Curves.ease;
|
|
167
|
-
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
|
|
168
|
-
return SlideTransition(
|
|
169
|
-
textDirection: slideDir,
|
|
170
|
-
position: animation.drive(tween),
|
|
171
|
-
child: child,
|
|
172
|
-
);
|
|
173
|
-
},
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
getBibleFromAsset(String file) async {
|
|
178
|
-
final bytes = await rootBundle.load("assets/bibles/$file.txt");
|
|
179
|
-
return getBibleFromText(utf8.decode(bytes.buffer.asUint8List(), allowMalformed: false));
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
openUrl(BuildContext context, String url) async {
|
|
183
|
-
final uri = Uri.parse(url);
|
|
184
|
-
if (await canLaunchUrl(uri)) {
|
|
185
|
-
if (await launchUrl(uri)) {
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
if (!context.mounted) return;
|
|
190
|
-
showError(context, context.l.urlError);
|
|
191
|
-
}
|
lib/widgets/bible_loader.dart
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import "package:flutter/material.dart";
|
|
2
|
+
import "package:only_bible_app/models.dart";
|
|
3
|
+
import "package:only_bible_app/state.dart";
|
|
4
|
+
|
|
5
|
+
class BibleLoader extends StatelessWidget {
|
|
6
|
+
final Function(Bible) builder;
|
|
7
|
+
|
|
8
|
+
const BibleLoader({super.key, required this.builder});
|
|
9
|
+
|
|
10
|
+
@override
|
|
11
|
+
Widget build(BuildContext context) {
|
|
12
|
+
return FutureBuilder(
|
|
13
|
+
future: bibleCache.watch(context),
|
|
14
|
+
builder: (context, snapshot) {
|
|
15
|
+
if (snapshot.connectionState == ConnectionState.done) {
|
|
16
|
+
return builder(snapshot.data!);
|
|
17
|
+
}
|
|
18
|
+
return ColoredBox(
|
|
19
|
+
color: Theme.of(context).colorScheme.background,
|
|
20
|
+
child: const Center(
|
|
21
|
+
child: CircularProgressIndicator(),
|
|
22
|
+
),
|
|
23
|
+
);
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
lib/widgets/scaffold_markdown.dart
CHANGED
|
@@ -29,7 +29,7 @@ class ScaffoldMarkdown extends StatelessWidget {
|
|
|
29
29
|
),
|
|
30
30
|
data: snapshot.data!,
|
|
31
31
|
onTapLink: (text, href, title) {
|
|
32
|
-
openUrl(
|
|
32
|
+
context.openUrl(href!);
|
|
33
33
|
},
|
|
34
34
|
);
|
|
35
35
|
}
|
pubspec.lock
CHANGED
|
@@ -628,14 +628,6 @@ packages:
|
|
|
628
628
|
url: "https://pub.dev"
|
|
629
629
|
source: hosted
|
|
630
630
|
version: "1.0.4"
|
|
631
|
-
nested:
|
|
632
|
-
dependency: transitive
|
|
633
|
-
description:
|
|
634
|
-
name: nested
|
|
635
|
-
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
|
636
|
-
url: "https://pub.dev"
|
|
637
|
-
source: hosted
|
|
638
|
-
version: "1.0.0"
|
|
639
631
|
package_config:
|
|
640
632
|
dependency: transitive
|
|
641
633
|
description:
|
|
@@ -764,14 +756,6 @@ packages:
|
|
|
764
756
|
url: "https://pub.dev"
|
|
765
757
|
source: hosted
|
|
766
758
|
version: "4.2.4"
|
|
767
|
-
provider:
|
|
768
|
-
dependency: "direct main"
|
|
769
|
-
description:
|
|
770
|
-
name: provider
|
|
771
|
-
sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
|
|
772
|
-
url: "https://pub.dev"
|
|
773
|
-
source: hosted
|
|
774
|
-
version: "6.0.5"
|
|
775
759
|
pub_semver:
|
|
776
760
|
dependency: transitive
|
|
777
761
|
description:
|
pubspec.yaml
CHANGED
|
@@ -21,7 +21,6 @@ dependencies:
|
|
|
21
21
|
flutter_swipe_detector: ^2.0.0
|
|
22
22
|
cupertino_icons: ^1.0.5
|
|
23
23
|
firebase_core: ^2.15.0
|
|
24
|
-
provider: ^6.0.5
|
|
25
24
|
firebase_crashlytics: ^3.3.4
|
|
26
25
|
firebase_performance: ^0.9.2+4
|
|
27
26
|
firebase_storage: ^11.2.5
|