~repos /only-bible-app

#kotlin#android#ios

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.


09ad9aac pyrossh

2 years ago
improve state
lib/app.dart CHANGED
@@ -6,28 +6,22 @@ import "package:only_bible_app/state.dart";
6
6
  import "package:only_bible_app/theme.dart";
7
7
 
8
8
  class App extends StatelessWidget {
9
- const App({super.key});
9
+ final int initialBook;
10
+ final int initialChapter;
11
+
12
+ const App({super.key, required this.initialBook, required this.initialChapter});
10
13
 
11
14
  @override
12
15
  Widget build(BuildContext context) {
13
16
  return MaterialApp(
14
- title: "Only Bible App",
17
+ title: "Only Bible App",
15
- localizationsDelegates: AppLocalizations.localizationsDelegates,
18
+ localizationsDelegates: AppLocalizations.localizationsDelegates,
16
- supportedLocales: AppLocalizations.supportedLocales,
19
+ supportedLocales: AppLocalizations.supportedLocales,
17
- debugShowCheckedModeBanner: false,
20
+ debugShowCheckedModeBanner: false,
18
- themeMode: darkMode.reactiveValue(context) ? ThemeMode.dark : ThemeMode.light,
21
+ themeMode: darkMode.reactiveValue(context) ? ThemeMode.dark : ThemeMode.light,
19
- theme: lightTheme,
22
+ theme: lightTheme,
20
- darkTheme: darkTheme,
23
+ darkTheme: darkTheme,
21
- home: FutureBuilder(
22
- future: loadPrefs(),
23
- builder: (context, snapshot) {
24
- if (snapshot.connectionState == ConnectionState.done) {
25
- return ChapterViewScreen(book: snapshot.data!.$1, chapter: snapshot.data!.$2);
24
+ home: ChapterViewScreen(book: initialBook, chapter: initialChapter),
26
- }
27
- return const Center(
28
- child: CircularProgressIndicator(),
29
- );
25
+ );
30
- },
31
- ));
32
26
  }
33
27
  }
lib/main.dart CHANGED
@@ -1,12 +1,14 @@
1
1
  import "package:flutter/material.dart";
2
2
  import "package:flutter/foundation.dart";
3
3
  import "package:firebase_core/firebase_core.dart";
4
+
4
5
  // import 'package:firebase_crashlytics/firebase_crashlytics.dart';
5
6
  import "package:only_bible_app/options.dart";
6
7
  import "package:flutter_persistent_value_notifier/flutter_persistent_value_notifier.dart";
7
8
  import "package:flutter_native_splash/flutter_native_splash.dart";
8
9
  import "package:only_bible_app/state.dart";
9
10
  import "package:only_bible_app/app.dart";
11
+ import "package:provider/provider.dart";
10
12
 
11
13
  // Toggle this to cause an async error to be thrown during initialization
12
14
  // and to test that runZonedGuarded() catches the error
@@ -15,7 +17,6 @@ const _kShouldTestAsyncErrorOnInit = false;
15
17
  // Toggle this for testing Crashlytics in your app locally.
16
18
  const _kTestingCrashlytics = true;
17
19
 
18
-
19
20
  void main() async {
20
21
  WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
21
22
  await Firebase.initializeApp(
@@ -30,9 +31,13 @@ void main() async {
30
31
  // };
31
32
  FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
32
33
  await initPersistentValueNotifier();
33
- await loadPrefs();
34
+ final (bible, book, chapter, darkMode, fontBold) = await loadData();
34
- await loadBible();
35
35
  await updateStatusBar();
36
- runApp(const App());
36
+ runApp(
37
+ ChangeNotifierProvider(
38
+ create: (context) => AppModel(bible: bible, darkMode: darkMode, fontBold: fontBold),
39
+ child: App(initialBook: book, initialChapter: chapter),
40
+ ),
41
+ );
37
42
  FlutterNativeSplash.remove();
38
43
  }
lib/screens/book_select_screen.dart CHANGED
@@ -1,12 +1,14 @@
1
1
  import "package:flutter/material.dart";
2
- import "package:only_bible_app/state.dart";
3
2
  import "package:only_bible_app/widgets/scaffold_menu.dart";
4
3
  import "package:only_bible_app/screens/chapter_select_screen.dart";
5
4
  import "package:only_bible_app/widgets/sliver_heading.dart";
6
5
  import "package:only_bible_app/widgets/sliver_tile_grid.dart";
6
+ import "package:only_bible_app/models.dart";
7
7
 
8
8
  class BookSelectScreen extends StatelessWidget {
9
+ final Bible bible;
10
+
9
- const BookSelectScreen({super.key});
11
+ const BookSelectScreen({super.key, required this.bible});
10
12
 
11
13
  onBookSelected(BuildContext context, int index) {
12
14
  Navigator.of(context).pushReplacement(
@@ -15,6 +17,7 @@ class BookSelectScreen extends StatelessWidget {
15
17
  transitionDuration: Duration.zero,
16
18
  reverseTransitionDuration: Duration.zero,
17
19
  pageBuilder: (context, _, __) => ChapterSelectScreen(
20
+ book: bible.books[index],
18
21
  selectedBookIndex: index,
19
22
  ),
20
23
  ),
@@ -29,7 +32,7 @@ class BookSelectScreen extends StatelessWidget {
29
32
  const SliverHeading(title: "Old Testament", showClose: true),
30
33
  SliverTileGrid(
31
34
  children: List.of(
32
- selectedBible.value!.getOldBooks().map((book) {
35
+ bible.getOldBooks().map((book) {
33
36
  return TextButton(
34
37
  child: Text(book.shortName()),
35
38
  onPressed: () => onBookSelected(context, book.index),
@@ -40,7 +43,7 @@ class BookSelectScreen extends StatelessWidget {
40
43
  const SliverHeading(title: "New Testament", top: 30, bottom: 20),
41
44
  SliverTileGrid(
42
45
  children: List.of(
43
- selectedBible.value!.getNewBooks().map((book) {
46
+ bible.getNewBooks().map((book) {
44
47
  return TextButton(
45
48
  child: Text(book.shortName()),
46
49
  onPressed: () => onBookSelected(context, book.index),
lib/screens/chapter_select_screen.dart CHANGED
@@ -1,4 +1,5 @@
1
1
  import "package:flutter/material.dart";
2
+ import "package:only_bible_app/models.dart";
2
3
  import "package:only_bible_app/state.dart";
3
4
  import "package:only_bible_app/widgets/scaffold_menu.dart";
4
5
  import "package:only_bible_app/widgets/sliver_tile_grid.dart";
@@ -6,9 +7,10 @@ import "package:only_bible_app/widgets/sliver_heading.dart";
6
7
  import "package:only_bible_app/screens/chapter_view_screen.dart";
7
8
 
8
9
  class ChapterSelectScreen extends StatelessWidget {
10
+ final Book book;
9
11
  final int selectedBookIndex;
10
12
 
11
- const ChapterSelectScreen({super.key, required this.selectedBookIndex});
13
+ const ChapterSelectScreen({super.key, required this.selectedBookIndex, required this.book});
12
14
 
13
15
  onChapterSelected(BuildContext context, int index) {
14
16
  Navigator.of(context).pushReplacement(
@@ -20,7 +22,6 @@ class ChapterSelectScreen extends StatelessWidget {
20
22
 
21
23
  @override
22
24
  Widget build(BuildContext context) {
23
- final book = selectedBible.value!.books[selectedBookIndex];
24
25
  return ScaffoldMenu(
25
26
  child: CustomScrollView(
26
27
  slivers: [
lib/screens/chapter_view_screen.dart CHANGED
@@ -16,6 +16,23 @@ class ChapterViewScreen extends StatelessWidget {
16
16
 
17
17
  @override
18
18
  Widget build(BuildContext context) {
19
+ // FutureBuilder(
20
+ // future: loadData(), // This reloads everytime theme changes
21
+ // builder: (context, snapshot) {
22
+ // if (snapshot.hasData && snapshot.data != null && snapshot.connectionState == ConnectionState.done) {
23
+ // return ChangeNotifierProvider(
24
+ // create: (_) => BibleViewModel(bible: snapshot.data!.$1),
25
+ // child: ChapterViewScreen(book: snapshot.data!.$2, chapter: snapshot.data!.$3),
26
+ // );
27
+ // }
28
+ // return ColoredBox(
29
+ // color: Theme.of(context).colorScheme.background,
30
+ // child: const Center(
31
+ // child: CircularProgressIndicator(),
32
+ // ),
33
+ // );
34
+ // },
35
+ // ),
19
36
  return ChangeNotifierProvider(
20
37
  create: (context) => ChapterViewModel(
21
38
  book: book,
lib/state.dart CHANGED
@@ -10,6 +10,39 @@ import "package:only_bible_app/models.dart";
10
10
  import "package:provider/provider.dart";
11
11
  import "package:shared_preferences/shared_preferences.dart";
12
12
 
13
+ class AppModel extends ChangeNotifier {
14
+ final Bible bible;
15
+ bool darkMode;
16
+ bool fontBold;
17
+ bool isPlaying = false;
18
+ int fontSizeDelta = 0;
19
+
20
+ AppModel({required this.bible, this.darkMode = false, this.fontBold = false});
21
+
22
+ static AppModel of(BuildContext context) {
23
+ return Provider.of(context, listen: true);
24
+ }
25
+
26
+ static AppModel ofEvent(BuildContext context) {
27
+ return Provider.of(context, listen: false);
28
+ }
29
+
30
+ save(int bibleId) async {
31
+ final prefs = await SharedPreferences.getInstance();
32
+ prefs.setInt("bibleId", bibleId);
33
+ // save darkTheme
34
+ // save fontBold
35
+ }
36
+
37
+ // changeBible() {
38
+ // save();
39
+ // }
40
+
41
+ // final Future<Bible>
42
+ // load() {
43
+ // }
44
+ }
45
+
13
46
  class ChapterViewModel extends ChangeNotifier {
14
47
  final int book;
15
48
  final int chapter;
@@ -17,15 +50,22 @@ class ChapterViewModel extends ChangeNotifier {
17
50
  final player = AudioPlayer();
18
51
 
19
52
  static ChapterViewModel of(BuildContext context) {
20
- return Provider.of<ChapterViewModel>(context, listen: true);
53
+ return Provider.of(context, listen: true);
21
54
  }
22
55
 
23
56
  static ChapterViewModel ofEvent(BuildContext context) {
24
- return Provider.of<ChapterViewModel>(context, listen: false);
57
+ return Provider.of(context, listen: false);
25
58
  }
26
59
 
27
60
  ChapterViewModel({required this.book, required this.chapter, required this.selectedVerses}) {
28
- savePrefs(book, chapter);
61
+ save(book, chapter);
62
+ }
63
+
64
+ save(int book, int chapter) async {
65
+ final prefs = await SharedPreferences.getInstance();
66
+ // prefs.setInt("bibleId", bibleId);
67
+ prefs.setInt("book", book);
68
+ prefs.setInt("chapter", chapter);
29
69
  }
30
70
 
31
71
  bool hasSelectedVerses() {
@@ -46,6 +86,7 @@ class ChapterViewModel extends ChangeNotifier {
46
86
  }
47
87
 
48
88
  onPlay(BuildContext context) async {
89
+ final bibleModel = AppModel.ofEvent(context);
49
90
  final model = ChapterViewModel.ofEvent(context);
50
91
  if (isPlaying.value) {
51
92
  await player.pause();
@@ -54,7 +95,7 @@ class ChapterViewModel extends ChangeNotifier {
54
95
  try {
55
96
  isPlaying.value = true;
56
97
  for (final v in selectedVerses) {
57
- final bibleName = selectedBible.value!.name;
98
+ final bibleName = bibleModel.bible.name;
58
99
  final book = (model.book + 1).toString().padLeft(2, "0");
59
100
  final chapter = (model.chapter + 1).toString().padLeft(3, "0");
60
101
  final verse = (v + 1).toString().padLeft(3, "0");
@@ -76,17 +117,22 @@ class ChapterViewModel extends ChangeNotifier {
76
117
  }
77
118
  }
78
119
 
79
- Future<(int, int)> loadPrefs() async {
120
+ Future<(Bible, int, int, bool, bool)> loadData() async {
80
121
  final prefs = await SharedPreferences.getInstance();
122
+ final bibleId = prefs.getInt("bibleId") ?? 1;
123
+ final book = prefs.getInt("book") ?? 0;
124
+ final chapter = prefs.getInt("chapter") ?? 0;
125
+ final darkMode = prefs.getBool("darkMode") ?? false;
126
+ final fontBold = prefs.getBool("fontBold") ?? false;
127
+ final selectedBible = bibles.firstWhere((it) => it.id == bibleId);
128
+ final books = await getBibleFromAsset(selectedBible.name);
129
+ final loadedBible = Bible.withBooks(
130
+ id: selectedBible.id,
131
+ name: selectedBible.name,
132
+ books: books,
133
+ );
81
134
  // await Future.delayed(Duration(seconds: 3));
82
- return (prefs.getInt("book") ?? 0, prefs.getInt("chapter") ?? 0);
83
- }
84
-
85
- savePrefs(int book, int chapter) async {
86
- final prefs = await SharedPreferences.getInstance();
87
- // prefs.setInt("bibleId", selectedBibleId.value);
88
- prefs.setInt("book", book);
89
- prefs.setInt("chapter", chapter);
135
+ return (loadedBible, book, chapter, darkMode, fontBold);
90
136
  }
91
137
 
92
138
  final darkMode = PersistentValueNotifier<bool>(
@@ -99,12 +145,6 @@ final fontBold = PersistentValueNotifier<bool>(
99
145
  initialValue: false,
100
146
  );
101
147
 
102
- final selectedBibleId = PersistentValueNotifier(
103
- sharedPreferencesKey: "selectedBibleId",
104
- initialValue: 1,
105
- );
106
-
107
- final selectedBible = ValueNotifier<Bible?>(null);
108
148
  final isPlaying = ValueNotifier(false);
109
149
  final fontSizeDelta = ValueNotifier(0);
110
150
 
@@ -152,9 +192,8 @@ updateStatusBar() {
152
192
  }
153
193
 
154
194
  changeBible(BuildContext context, int i) {
155
- selectedBibleId.value = i;
156
195
  // TODO: maybe use a future as the bible needs to load
157
- loadBible();
196
+ // loadBible();
158
197
  Navigator.of(context).pop();
159
198
  }
160
199
 
@@ -203,39 +242,31 @@ navigateBookChapter(BuildContext context, int book, int chapter, TextDirection?
203
242
  }
204
243
 
205
244
  onNext(BuildContext context, int book, int chapter) {
245
+ final selectedBible = AppModel.of(context).bible;
206
- final selectedBook = selectedBible.value!.books[book];
246
+ final selectedBook = selectedBible.books[book];
207
247
  if (selectedBook.chapters.length > chapter + 1) {
208
248
  navigateBookChapter(context, selectedBook.index, chapter + 1, TextDirection.ltr);
209
249
  } else {
210
- if (selectedBook.index + 1 < selectedBible.value!.books.length) {
250
+ if (selectedBook.index + 1 < selectedBible.books.length) {
211
- final nextBook = selectedBible.value!.books[selectedBook.index + 1];
251
+ final nextBook = selectedBible.books[selectedBook.index + 1];
212
252
  navigateBookChapter(context, nextBook.index, 0, TextDirection.ltr);
213
253
  }
214
254
  }
215
255
  }
216
256
 
217
257
  onPrevious(BuildContext context, int book, int chapter) {
258
+ final selectedBible = AppModel.of(context).bible;
218
- final selectedBook = selectedBible.value!.books[book];
259
+ final selectedBook = selectedBible.books[book];
219
260
  if (chapter - 1 >= 0) {
220
261
  navigateBookChapter(context, selectedBook.index, chapter - 1, TextDirection.rtl);
221
262
  } else {
222
263
  if (selectedBook.index - 1 >= 0) {
223
- final prevBook = selectedBible.value!.books[selectedBook.index - 1];
264
+ final prevBook = selectedBible.books[selectedBook.index - 1];
224
265
  navigateBookChapter(context, prevBook.index, prevBook.chapters.length - 1, TextDirection.rtl);
225
266
  }
226
267
  }
227
268
  }
228
269
 
229
- loadBible() async {
230
- final bible = bibles.firstWhere((it) => it.id == selectedBibleId.value);
231
- final books = await getBibleFromAsset(bible.name);
232
- selectedBible.value = Bible.withBooks(
233
- id: bible.id,
234
- name: bible.name,
235
- books: books,
236
- );
237
- }
238
-
239
270
  getBibleFromAsset(String file) async {
240
271
  final bytes = await rootBundle.load("assets/bibles/$file.txt");
241
272
  return getBibleFromText(utf8.decode(bytes.buffer.asUint8List(), allowMalformed: false));
lib/widgets/header.dart CHANGED
@@ -1,5 +1,4 @@
1
1
  import "package:flutter/material.dart";
2
- import "package:flutter_reactive_value/flutter_reactive_value.dart";
3
2
  import "package:only_bible_app/screens/bible_select_screen.dart";
4
3
  import "package:only_bible_app/screens/book_select_screen.dart";
5
4
  import "package:only_bible_app/widgets/play_button.dart";
@@ -11,9 +10,9 @@ class Header extends StatelessWidget {
11
10
 
12
11
  @override
13
12
  Widget build(BuildContext context) {
13
+ final selectedBible = AppModel.of(context).bible;
14
- final book = ChapterViewModel.of(context).book;
14
+ final model = ChapterViewModel.of(context);
15
- final chapter = ChapterViewModel.of(context).chapter;
16
- final selectedBook = selectedBible.value!.books[book];
15
+ final selectedBook = selectedBible.books[model.book];
17
16
  final isDesktop = isWide(context);
18
17
  return Container(
19
18
  padding: EdgeInsets.only(
@@ -40,7 +39,7 @@ class Header extends StatelessWidget {
40
39
  color: Theme.of(context).textTheme.headlineMedium!.color,
41
40
  ),
42
41
  icon: Text(
43
- "${selectedBook.name} ${chapter + 1}",
42
+ "${selectedBook.name} ${model.chapter + 1}",
44
43
  style: Theme.of(context).textTheme.headlineMedium,
45
44
  ),
46
45
  onPressed: () {
@@ -50,7 +49,7 @@ class Header extends StatelessWidget {
50
49
  opaque: false,
51
50
  transitionDuration: Duration.zero,
52
51
  reverseTransitionDuration: Duration.zero,
53
- pageBuilder: (context, _, __) => const BookSelectScreen(),
52
+ pageBuilder: (context, _, __) => BookSelectScreen(bible: selectedBible),
54
53
  ),
55
54
  );
56
55
  },
@@ -66,7 +65,7 @@ class Header extends StatelessWidget {
66
65
  style: TextButton.styleFrom(
67
66
  padding: const EdgeInsets.symmetric(horizontal: 20),
68
67
  ),
69
- child: Text(selectedBible.reactiveValue(context)!.name),
68
+ child: Text(selectedBible.name),
70
69
  onPressed: () {
71
70
  Navigator.of(context).push(
72
71
  PageRouteBuilder(
lib/widgets/verse_list.dart CHANGED
@@ -8,10 +8,9 @@ class VerseList extends StatelessWidget {
8
8
 
9
9
  @override
10
10
  Widget build(BuildContext context) {
11
+ final selectedBible = AppModel.of(context).bible;
11
- final book = ChapterViewModel.of(context).book;
12
+ final model = ChapterViewModel.of(context);
12
- final chapter = ChapterViewModel.of(context).chapter;
13
- final selectedBook = selectedBible.reactiveValue(context)!.books[book];
14
- final verses = selectedBook.chapters[chapter].verses;
13
+ final verses = selectedBible.books[model.book].chapters[model.chapter].verses;
15
14
  return SelectionArea(
16
15
  child: ListView.builder(
17
16
  shrinkWrap: false,