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


cfa48256 pyrossh

2 years ago
Move state
android/app/build.gradle CHANGED
@@ -54,7 +54,7 @@ android {
54
54
  // You can update the following values to match your application needs.
55
55
  // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
56
56
  minSdkVersion 30
57
- targetSdkVersion 34
57
+ targetSdkVersion 32
58
58
  versionCode flutterVersionCode.toInteger()
59
59
  versionName flutterVersionName
60
60
  }
@@ -73,5 +73,15 @@ flutter {
73
73
  }
74
74
 
75
75
  dependencies {
76
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
76
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
77
77
  }
78
+
79
+ configurations.all {
80
+ resolutionStrategy {
81
+ eachDependency {
82
+ if ((requested.group == "org.jetbrains.kotlin") && (requested.name.startsWith("kotlin-stdlib"))) {
83
+ useVersion("1.8.0")
84
+ }
85
+ }
86
+ }
87
+ }
lib/app.dart CHANGED
@@ -1,7 +1,7 @@
1
1
  import "package:flutter/material.dart";
2
2
  import "package:flutter_gen/gen_l10n/app_localizations.dart";
3
+ import "package:only_bible_app/providers/app_model.dart";
3
4
  import "package:only_bible_app/screens/chapter_view_screen.dart";
4
- import "package:only_bible_app/state.dart";
5
5
  import "package:only_bible_app/theme.dart";
6
6
 
7
7
  class App extends StatelessWidget {
lib/main.dart CHANGED
@@ -4,8 +4,8 @@ import "package:firebase_core/firebase_core.dart";
4
4
  import "package:firebase_crashlytics/firebase_crashlytics.dart";
5
5
  import "package:only_bible_app/firebase_options.dart";
6
6
  import "package:flutter_native_splash/flutter_native_splash.dart";
7
- import "package:only_bible_app/state.dart";
8
7
  import "package:only_bible_app/app.dart";
8
+ import "package:only_bible_app/providers/app_model.dart";
9
9
  import "package:provider/provider.dart";
10
10
 
11
11
  void main() async {
lib/providers/app_model.dart ADDED
@@ -0,0 +1,235 @@
1
+ // import "package:firebase_performance/firebase_performance.dart";
2
+ import "package:flutter/services.dart";
3
+ import "package:flutter/material.dart";
4
+ import "package:only_bible_app/screens/bible_select_screen.dart";
5
+ import "package:only_bible_app/screens/book_select_screen.dart";
6
+ import "package:only_bible_app/models.dart";
7
+ import "package:only_bible_app/widgets/actions_sheet.dart";
8
+ import "package:only_bible_app/widgets/note_sheet.dart";
9
+ import "package:only_bible_app/widgets/settings_sheet.dart";
10
+ import "package:provider/provider.dart";
11
+ import "package:shared_preferences/shared_preferences.dart";
12
+ import "package:get_storage/get_storage.dart";
13
+ import "package:only_bible_app/utils.dart";
14
+
15
+ class HistoryFrame {
16
+ final int book;
17
+ final int chapter;
18
+ final int? verse;
19
+
20
+ const HistoryFrame({required this.book, required this.chapter, this.verse});
21
+ }
22
+
23
+ class AppModel extends ChangeNotifier {
24
+ String languageCode = "en";
25
+ Bible bible = bibles.first;
26
+ bool darkMode = false;
27
+ bool fontBold = false;
28
+ double textScaleFactor = 0;
29
+ bool actionsShown = false;
30
+ final TextEditingController noteTextController = TextEditingController();
31
+ List<HistoryFrame> history = [];
32
+ final box = GetStorage("only-bible-app-backup");
33
+
34
+ static AppModel of(BuildContext context) {
35
+ return Provider.of(context, listen: true);
36
+ }
37
+
38
+ static AppModel ofEvent(BuildContext context) {
39
+ return Provider.of(context, listen: false);
40
+ }
41
+
42
+ save() async {
43
+ final prefs = await SharedPreferences.getInstance();
44
+ await prefs.setInt("bibleId", bible.id);
45
+ await prefs.setBool("darkMode", darkMode);
46
+ await prefs.setBool("fontBold", fontBold);
47
+ await prefs.setDouble("textScaleFactor", textScaleFactor);
48
+ }
49
+
50
+ Future<(int, int)> loadData() async {
51
+ final prefs = await SharedPreferences.getInstance();
52
+ final bibleId = prefs.getInt("bibleId") ?? 1;
53
+ darkMode = prefs.getBool("darkMode") ?? false;
54
+ fontBold = prefs.getBool("fontBold") ?? false;
55
+ textScaleFactor = prefs.getDouble("textScaleFactor") ?? 1;
56
+ bible = await loadBible(bibleId);
57
+ // await Future.delayed(Duration(seconds: 3));
58
+ final book = prefs.getInt("book") ?? 0;
59
+ final chapter = prefs.getInt("chapter") ?? 0;
60
+ updateStatusBar();
61
+ return (book, chapter);
62
+ }
63
+
64
+ Future<Bible> loadBible(int id) async {
65
+ final selectedBible = bibles.firstWhere((it) => it.id == id);
66
+ // Trace customTrace;
67
+ // if (!isDesktop()) {
68
+ // customTrace = FirebasePerformance.instance.newTrace("loadBible");
69
+ // await customTrace.start();
70
+ // }
71
+ final books = await getBibleFromAsset(languageCode, selectedBible.name);
72
+ // if (!isDesktop()) {
73
+ // await customTrace.stop();
74
+ // }
75
+ return Bible.withBooks(
76
+ id: selectedBible.id,
77
+ name: selectedBible.name,
78
+ hasAudio: selectedBible.hasAudio,
79
+ books: books,
80
+ );
81
+ }
82
+
83
+ changeBible(BuildContext context) {
84
+ Navigator.of(context).pushReplacement(
85
+ createNoTransitionPageRoute(
86
+ const BibleSelectScreen(),
87
+ ),
88
+ );
89
+ }
90
+
91
+ changeBibleFromHeader(BuildContext context) {
92
+ Navigator.of(context).push(
93
+ createNoTransitionPageRoute(
94
+ const BibleSelectScreen(),
95
+ ),
96
+ );
97
+ }
98
+
99
+ updateCurrentBible(BuildContext context, int id) async {
100
+ // TODO: maybe use a future as the bible needs to load
101
+ bible = await loadBible(id);
102
+ notifyListeners();
103
+ save();
104
+ }
105
+
106
+ changeBook(BuildContext context) {
107
+ Navigator.of(context).push(
108
+ createNoTransitionPageRoute(
109
+ BookSelectScreen(bible: bible),
110
+ ),
111
+ );
112
+ }
113
+
114
+ toggleMode() async {
115
+ darkMode = !darkMode;
116
+ updateStatusBar();
117
+ notifyListeners();
118
+ save();
119
+ }
120
+
121
+ updateStatusBar() {
122
+ if (darkMode) {
123
+ SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
124
+ systemNavigationBarColor: Color(0xFF1F1F22),
125
+ statusBarColor: Color(0xFF1F1F22),
126
+ systemNavigationBarIconBrightness: Brightness.light,
127
+ statusBarIconBrightness: Brightness.light,
128
+ ));
129
+ } else {
130
+ SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
131
+ systemNavigationBarColor: Colors.white,
132
+ statusBarColor: Colors.white,
133
+ systemNavigationBarIconBrightness: Brightness.dark,
134
+ statusBarIconBrightness: Brightness.dark,
135
+ ));
136
+ }
137
+ }
138
+
139
+ toggleBold() async {
140
+ fontBold = !fontBold;
141
+ notifyListeners();
142
+ save();
143
+ }
144
+
145
+ increaseFont() async {
146
+ textScaleFactor += 0.1;
147
+ notifyListeners();
148
+ save();
149
+ }
150
+
151
+ decreaseFont() async {
152
+ textScaleFactor -= 0.1;
153
+ notifyListeners();
154
+ save();
155
+ }
156
+
157
+ showSettings(BuildContext context) {
158
+ // if (isWide(context)) {
159
+ // Navigator.of(context).push(
160
+ // createNoTransitionPageRoute(
161
+ // const ScaffoldMenu(
162
+ // backgroundColor: Color(0xFFF2F2F7),
163
+ // child: SettingsSheet(),
164
+ // ),
165
+ // ),
166
+ // );
167
+ // } else {
168
+ showModalBottomSheet(
169
+ context: context,
170
+ isDismissible: true,
171
+ enableDrag: true,
172
+ showDragHandle: true,
173
+ useSafeArea: true,
174
+ builder: (context) => const SettingsSheet(),
175
+ );
176
+ // }
177
+ }
178
+
179
+ showActions(BuildContext context) {
180
+ actionsShown = true;
181
+ Scaffold.of(context).showBottomSheet(
182
+ enableDrag: false,
183
+ (context) => const ActionsSheet(),
184
+ );
185
+ notifyListeners();
186
+ }
187
+
188
+ hideActions(BuildContext context) {
189
+ if (actionsShown) {
190
+ actionsShown = false;
191
+ Navigator.of(context).pop();
192
+ notifyListeners();
193
+ }
194
+ }
195
+
196
+ bool hasNote(Verse v) {
197
+ return box.hasData("${v.book}:${v.chapter}:${v.index}:note");
198
+ }
199
+
200
+ showNoteField(BuildContext context, Verse v) {
201
+ final noteText = box.read("${v.book}:${v.chapter}:${v.index}:note") ?? "";
202
+ noteTextController.text = noteText;
203
+ showModalBottomSheet(
204
+ context: context,
205
+ isDismissible: true,
206
+ enableDrag: true,
207
+ showDragHandle: true,
208
+ useSafeArea: true,
209
+ isScrollControlled: true,
210
+ builder: (context) => NoteSheet(verse: v),
211
+ );
212
+ }
213
+
214
+ saveNote(BuildContext context, Verse v) {
215
+ final note = noteTextController.text;
216
+ box.write("${v.book}:${v.chapter}:${v.index}:note", note);
217
+ box.save();
218
+ // Close the bottom sheet
219
+ // if (!mounted) return;
220
+ // Navigator.of(context).pop();
221
+ notifyListeners();
222
+ hideNoteField(context);
223
+ }
224
+
225
+ deleteNote(BuildContext context, Verse v) {
226
+ box.remove("${v.book}:${v.chapter}:${v.index}:note");
227
+ box.save();
228
+ notifyListeners();
229
+ hideNoteField(context);
230
+ }
231
+
232
+ hideNoteField(BuildContext context) {
233
+ Navigator.of(context).pop();
234
+ }
235
+ }
lib/providers/chapter_view_model.dart ADDED
@@ -0,0 +1,187 @@
1
+ import "dart:developer";
2
+ import "package:firebase_crashlytics/firebase_crashlytics.dart";
3
+ import "package:firebase_storage/firebase_storage.dart";
4
+ import "package:flutter/services.dart";
5
+ import "package:flutter/material.dart";
6
+ import "package:just_audio/just_audio.dart";
7
+ import "package:only_bible_app/screens/chapter_view_screen.dart";
8
+ import "package:only_bible_app/dialog.dart";
9
+ import "package:only_bible_app/models.dart";
10
+ import "package:provider/provider.dart";
11
+ import "package:share_plus/share_plus.dart";
12
+ import "package:shared_preferences/shared_preferences.dart";
13
+ import "package:only_bible_app/utils.dart";
14
+ import "package:only_bible_app/providers/app_model.dart";
15
+
16
+ class ChapterViewModel extends ChangeNotifier {
17
+ final int book;
18
+ final int chapter;
19
+ final List<Verse> selectedVerses;
20
+ final player = AudioPlayer();
21
+ bool isPlaying = false;
22
+
23
+ static ChapterViewModel of(BuildContext context) {
24
+ return Provider.of(context, listen: true);
25
+ }
26
+
27
+ static ChapterViewModel ofEvent(BuildContext context) {
28
+ return Provider.of(context, listen: false);
29
+ }
30
+
31
+ static Book selectedBook(BuildContext context) {
32
+ final model = of(context);
33
+ return AppModel.of(context).bible.books[model.book];
34
+ }
35
+
36
+ static Chapter selectedChapter(BuildContext context) {
37
+ final model = of(context);
38
+ return AppModel.of(context).bible.books[model.book].chapters[model.chapter];
39
+ }
40
+
41
+ ChapterViewModel({required this.book, required this.chapter, required this.selectedVerses}) {
42
+ save(book, chapter);
43
+ }
44
+
45
+ save(int book, int chapter) async {
46
+ final prefs = await SharedPreferences.getInstance();
47
+ prefs.setInt("book", book);
48
+ prefs.setInt("chapter", chapter);
49
+ }
50
+
51
+ navigateBookChapter(BuildContext context, int book, int chapter, TextDirection? dir) {
52
+ if (isPlaying) {
53
+ pause();
54
+ }
55
+ AppModel.ofEvent(context).hideActions(context);
56
+ Navigator.of(context).push(
57
+ createSlideRoute(
58
+ context: context,
59
+ slideDir: dir,
60
+ page: ChapterViewScreen(book: book, chapter: chapter),
61
+ ),
62
+ );
63
+ }
64
+
65
+ onNext(BuildContext context, int book, int chapter) {
66
+ final selectedBible = AppModel.ofEvent(context).bible;
67
+ final selectedBook = selectedBible.books[book];
68
+ if (selectedBook.chapters.length > chapter + 1) {
69
+ navigateBookChapter(context, selectedBook.index, chapter + 1, TextDirection.ltr);
70
+ } else {
71
+ if (selectedBook.index + 1 < selectedBible.books.length) {
72
+ final nextBook = selectedBible.books[selectedBook.index + 1];
73
+ navigateBookChapter(context, nextBook.index, 0, TextDirection.ltr);
74
+ }
75
+ }
76
+ }
77
+
78
+ onPrevious(BuildContext context, int book, int chapter) {
79
+ final selectedBible = AppModel.ofEvent(context).bible;
80
+ final selectedBook = selectedBible.books[book];
81
+ if (chapter - 1 >= 0) {
82
+ // if (Navigator.of(context).canPop()) {
83
+ // Navigator.of(context).pop();
84
+ // } else {
85
+ navigateBookChapter(context, selectedBook.index, chapter - 1, TextDirection.rtl);
86
+ // }
87
+ } else {
88
+ if (selectedBook.index - 1 >= 0) {
89
+ final prevBook = selectedBible.books[selectedBook.index - 1];
90
+ navigateBookChapter(context, prevBook.index, prevBook.chapters.length - 1, TextDirection.rtl);
91
+ }
92
+ }
93
+ }
94
+
95
+ bool hasSelectedVerses() {
96
+ return selectedVerses.isNotEmpty;
97
+ }
98
+
99
+ void clearSelections(BuildContext context) {
100
+ selectedVerses.clear();
101
+ AppModel.ofEvent(context).hideActions(context);
102
+ notifyListeners();
103
+ }
104
+
105
+ bool isVerseSelected(Verse v) {
106
+ return selectedVerses.any((el) => el.index == v.index);
107
+ }
108
+
109
+ bool isVerseHighlighted(BuildContext context) {
110
+ // box.read("${book}:${chapter}:${verse}", "color");
111
+ return false;
112
+ }
113
+
114
+ void onVerseSelected(BuildContext context, Verse v) {
115
+ if (selectedVerses.isEmpty) {
116
+ AppModel.ofEvent(context).showActions(context);
117
+ }
118
+ if (isVerseSelected(v)) {
119
+ selectedVerses.removeWhere((it) => it.index == v.index);
120
+ } else {
121
+ selectedVerses.add(v);
122
+ }
123
+ if (selectedVerses.isEmpty) {
124
+ AppModel.ofEvent(context).hideActions(context);
125
+ }
126
+ notifyListeners();
127
+ }
128
+
129
+ void copyVerses() {
130
+ final text = selectedVerses.map((e) => e.text).join("\n");
131
+ Clipboard.setData(ClipboardData(text: text));
132
+ // maybe close the action menu or show a snackbar on iOS (android already does this)
133
+ // ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Copied to clipboard")));
134
+ }
135
+
136
+ void shareVerses(BuildContext context) {
137
+ final bible = AppModel.ofEvent(context).bible;
138
+ final name = bible.books[selectedVerses.first.book].name;
139
+ final chapter = selectedVerses.first.chapter + 1;
140
+ final title = "$name $chapter: ${selectedVerses.map((e) => e.index + 1).join(", ")}";
141
+ final text = selectedVerses.map((e) => e.text).join("\n");
142
+ Share.share("$title\n$text", subject: title);
143
+ }
144
+
145
+ pause() async {
146
+ await player.pause();
147
+ isPlaying = false;
148
+ notifyListeners();
149
+ }
150
+
151
+ onPlay(BuildContext context) async {
152
+ final bible = AppModel.ofEvent(context).bible;
153
+ if (!bible.hasAudio) {
154
+ showError(
155
+ context,
156
+ "This Bible doesn't support audio. Currently audio is only available for the Kannada Bible.",
157
+ );
158
+ return;
159
+ }
160
+ if (isPlaying) {
161
+ pause();
162
+ } else {
163
+ isPlaying = true;
164
+ notifyListeners();
165
+ for (final v in selectedVerses) {
166
+ final bibleName = bible.name;
167
+ final book = (v.book + 1).toString().padLeft(2, "0");
168
+ final chapter = (v.chapter + 1).toString().padLeft(3, "0");
169
+ final verseNo = (v.index + 1).toString().padLeft(3, "0");
170
+ final pathname = "$bibleName/$book-$chapter-$verseNo.mp3";
171
+ try {
172
+ final url = await FirebaseStorage.instance.ref(pathname).getDownloadURL();
173
+ await player.setUrl(url);
174
+ await player.play();
175
+ await player.stop();
176
+ } catch (err) {
177
+ log("Could not play audio", name: "play", error: (err.toString(), pathname));
178
+ FirebaseCrashlytics.instance.recordFlutterError(FlutterErrorDetails(exception: (err.toString(), pathname)));
179
+ showError(context, "Could not play audio");
180
+ return;
181
+ } finally {
182
+ pause();
183
+ }
184
+ }
185
+ }
186
+ }
187
+ }
lib/screens/bible_select_screen.dart CHANGED
@@ -1,6 +1,6 @@
1
1
  import "package:flutter/material.dart";
2
- import "package:only_bible_app/state.dart";
3
2
  import "package:only_bible_app/models.dart";
3
+ import "package:only_bible_app/providers/app_model.dart";
4
4
  import "package:only_bible_app/widgets/scaffold_menu.dart";
5
5
  import "package:only_bible_app/widgets/sliver_heading.dart";
6
6
  import "package:only_bible_app/widgets/sliver_tile_grid.dart";
lib/screens/chapter_select_screen.dart CHANGED
@@ -1,6 +1,6 @@
1
1
  import "package:flutter/material.dart";
2
2
  import "package:only_bible_app/models.dart";
3
- import "package:only_bible_app/state.dart";
3
+ import "package:only_bible_app/utils.dart";
4
4
  import "package:only_bible_app/widgets/scaffold_menu.dart";
5
5
  import "package:only_bible_app/widgets/sliver_tile_grid.dart";
6
6
  import "package:only_bible_app/widgets/sliver_heading.dart";
lib/screens/chapter_view_screen.dart CHANGED
@@ -1,6 +1,7 @@
1
1
  import "package:flutter/material.dart";
2
+ import "package:only_bible_app/providers/chapter_view_model.dart";
3
+ import "package:only_bible_app/utils.dart";
2
4
  import "package:only_bible_app/widgets/chapter_app_bar.dart";
3
- import "package:only_bible_app/state.dart";
4
5
  import "package:only_bible_app/widgets/sidebar.dart";
5
6
  import "package:only_bible_app/widgets/verses_view.dart";
6
7
  import "package:provider/provider.dart";
lib/state.dart DELETED
@@ -1,474 +0,0 @@
1
- import "dart:convert";
2
- import "dart:developer";
3
- import "package:firebase_crashlytics/firebase_crashlytics.dart";
4
- import "package:firebase_storage/firebase_storage.dart";
5
-
6
- // import "package:firebase_performance/firebase_performance.dart";
7
- import "package:flutter/foundation.dart" show defaultTargetPlatform, TargetPlatform;
8
- import "package:flutter/services.dart";
9
- import "package:flutter/material.dart";
10
- import "package:just_audio/just_audio.dart";
11
- import "package:only_bible_app/screens/bible_select_screen.dart";
12
- import "package:only_bible_app/screens/book_select_screen.dart";
13
- import "package:only_bible_app/screens/chapter_view_screen.dart";
14
- import "package:only_bible_app/dialog.dart";
15
- import "package:only_bible_app/models.dart";
16
- import "package:only_bible_app/widgets/actions_sheet.dart";
17
- import "package:only_bible_app/widgets/note_sheet.dart";
18
- import "package:only_bible_app/widgets/scaffold_menu.dart";
19
- import "package:only_bible_app/widgets/settings_sheet.dart";
20
- import "package:provider/provider.dart";
21
- import "package:share_plus/share_plus.dart";
22
- import "package:shared_preferences/shared_preferences.dart";
23
- import "package:get_storage/get_storage.dart";
24
-
25
- class HistoryFrame {
26
- final int book;
27
- final int chapter;
28
- final int? verse;
29
-
30
- const HistoryFrame({required this.book, required this.chapter, this.verse});
31
- }
32
-
33
- class AppModel extends ChangeNotifier {
34
- String languageCode = "en";
35
- Bible bible = bibles.first;
36
- bool darkMode = false;
37
- bool fontBold = false;
38
- double textScaleFactor = 0;
39
- bool actionsShown = false;
40
- final TextEditingController noteTextController = TextEditingController();
41
- List<HistoryFrame> history = [];
42
- final box = GetStorage("only-bible-app-backup");
43
-
44
- static AppModel of(BuildContext context) {
45
- return Provider.of(context, listen: true);
46
- }
47
-
48
- static AppModel ofEvent(BuildContext context) {
49
- return Provider.of(context, listen: false);
50
- }
51
-
52
- save() async {
53
- final prefs = await SharedPreferences.getInstance();
54
- await prefs.setInt("bibleId", bible.id);
55
- await prefs.setBool("darkMode", darkMode);
56
- await prefs.setBool("fontBold", fontBold);
57
- await prefs.setDouble("textScaleFactor", textScaleFactor);
58
- }
59
-
60
- Future<(int, int)> loadData() async {
61
- final prefs = await SharedPreferences.getInstance();
62
- final bibleId = prefs.getInt("bibleId") ?? 1;
63
- darkMode = prefs.getBool("darkMode") ?? false;
64
- fontBold = prefs.getBool("fontBold") ?? false;
65
- textScaleFactor = prefs.getDouble("textScaleFactor") ?? 1;
66
- bible = await loadBible(bibleId);
67
- // await Future.delayed(Duration(seconds: 3));
68
- final book = prefs.getInt("book") ?? 0;
69
- final chapter = prefs.getInt("chapter") ?? 0;
70
- updateStatusBar();
71
- return (book, chapter);
72
- }
73
-
74
- Future<Bible> loadBible(int id) async {
75
- final selectedBible = bibles.firstWhere((it) => it.id == id);
76
- // Trace customTrace;
77
- // if (!isDesktop()) {
78
- // customTrace = FirebasePerformance.instance.newTrace("loadBible");
79
- // await customTrace.start();
80
- // }
81
- final books = await getBibleFromAsset(languageCode, selectedBible.name);
82
- // if (!isDesktop()) {
83
- // await customTrace.stop();
84
- // }
85
- return Bible.withBooks(
86
- id: selectedBible.id,
87
- name: selectedBible.name,
88
- hasAudio: selectedBible.hasAudio,
89
- books: books,
90
- );
91
- }
92
-
93
- changeBible(BuildContext context) {
94
- Navigator.of(context).pushReplacement(
95
- createNoTransitionPageRoute(
96
- const BibleSelectScreen(),
97
- ),
98
- );
99
- }
100
-
101
- changeBibleFromHeader(BuildContext context) {
102
- Navigator.of(context).push(
103
- createNoTransitionPageRoute(
104
- const BibleSelectScreen(),
105
- ),
106
- );
107
- }
108
-
109
- updateCurrentBible(BuildContext context, int id) async {
110
- // TODO: maybe use a future as the bible needs to load
111
- bible = await loadBible(id);
112
- notifyListeners();
113
- save();
114
- }
115
-
116
- changeBook(BuildContext context) {
117
- Navigator.of(context).push(
118
- createNoTransitionPageRoute(
119
- BookSelectScreen(bible: bible),
120
- ),
121
- );
122
- }
123
-
124
- toggleMode() async {
125
- darkMode = !darkMode;
126
- updateStatusBar();
127
- notifyListeners();
128
- save();
129
- }
130
-
131
- updateStatusBar() {
132
- if (darkMode) {
133
- SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
134
- systemNavigationBarColor: Color(0xFF1F1F22),
135
- statusBarColor: Color(0xFF1F1F22),
136
- systemNavigationBarIconBrightness: Brightness.light,
137
- statusBarIconBrightness: Brightness.light,
138
- ));
139
- } else {
140
- SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
141
- systemNavigationBarColor: Colors.white,
142
- statusBarColor: Colors.white,
143
- systemNavigationBarIconBrightness: Brightness.dark,
144
- statusBarIconBrightness: Brightness.dark,
145
- ));
146
- }
147
- }
148
-
149
- toggleBold() async {
150
- fontBold = !fontBold;
151
- notifyListeners();
152
- save();
153
- }
154
-
155
- increaseFont() async {
156
- textScaleFactor += 0.1;
157
- notifyListeners();
158
- save();
159
- }
160
-
161
- decreaseFont() async {
162
- textScaleFactor -= 0.1;
163
- notifyListeners();
164
- save();
165
- }
166
-
167
- showSettings(BuildContext context) {
168
- // if (isWide(context)) {
169
- // Navigator.of(context).push(
170
- // createNoTransitionPageRoute(
171
- // const ScaffoldMenu(
172
- // backgroundColor: Color(0xFFF2F2F7),
173
- // child: SettingsSheet(),
174
- // ),
175
- // ),
176
- // );
177
- // } else {
178
- showModalBottomSheet(
179
- context: context,
180
- isDismissible: true,
181
- enableDrag: true,
182
- showDragHandle: true,
183
- useSafeArea: true,
184
- builder: (context) => const SettingsSheet(),
185
- );
186
- // }
187
- }
188
-
189
- showActions(BuildContext context) {
190
- actionsShown = true;
191
- Scaffold.of(context).showBottomSheet(
192
- enableDrag: false,
193
- (context) => const ActionsSheet(),
194
- );
195
- notifyListeners();
196
- }
197
-
198
- hideActions(BuildContext context) {
199
- if (actionsShown) {
200
- actionsShown = false;
201
- Navigator.of(context).pop();
202
- notifyListeners();
203
- }
204
- }
205
-
206
- bool hasNote(Verse v) {
207
- return box.hasData("${v.book}:${v.chapter}:${v.index}:note");
208
- }
209
-
210
- showNoteField(BuildContext context, Verse v) {
211
- final noteText = box.read("${v.book}:${v.chapter}:${v.index}:note") ?? "";
212
- noteTextController.text = noteText;
213
- showModalBottomSheet(
214
- context: context,
215
- isDismissible: true,
216
- enableDrag: true,
217
- showDragHandle: true,
218
- useSafeArea: true,
219
- isScrollControlled: true,
220
- builder: (context) => NoteSheet(verse: v),
221
- );
222
- }
223
-
224
- saveNote(BuildContext context, Verse v) {
225
- final note = noteTextController.text;
226
- box.write("${v.book}:${v.chapter}:${v.index}:note", note);
227
- box.save();
228
- // Close the bottom sheet
229
- // if (!mounted) return;
230
- // Navigator.of(context).pop();
231
- notifyListeners();
232
- hideNoteField(context);
233
- }
234
-
235
- deleteNote(BuildContext context, Verse v) {
236
- box.remove("${v.book}:${v.chapter}:${v.index}:note");
237
- box.save();
238
- notifyListeners();
239
- hideNoteField(context);
240
- }
241
-
242
- hideNoteField(BuildContext context) {
243
- Navigator.of(context).pop();
244
- }
245
- }
246
-
247
- class ChapterViewModel extends ChangeNotifier {
248
- final int book;
249
- final int chapter;
250
- final List<Verse> selectedVerses;
251
- final player = AudioPlayer();
252
- bool isPlaying = false;
253
-
254
- static ChapterViewModel of(BuildContext context) {
255
- return Provider.of(context, listen: true);
256
- }
257
-
258
- static ChapterViewModel ofEvent(BuildContext context) {
259
- return Provider.of(context, listen: false);
260
- }
261
-
262
- static Book selectedBook(BuildContext context) {
263
- final model = of(context);
264
- return AppModel.of(context).bible.books[model.book];
265
- }
266
-
267
- static Chapter selectedChapter(BuildContext context) {
268
- final model = of(context);
269
- return AppModel.of(context).bible.books[model.book].chapters[model.chapter];
270
- }
271
-
272
- ChapterViewModel({required this.book, required this.chapter, required this.selectedVerses}) {
273
- save(book, chapter);
274
- }
275
-
276
- save(int book, int chapter) async {
277
- final prefs = await SharedPreferences.getInstance();
278
- prefs.setInt("book", book);
279
- prefs.setInt("chapter", chapter);
280
- }
281
-
282
- navigateBookChapter(BuildContext context, int book, int chapter, TextDirection? dir) {
283
- if (isPlaying) {
284
- pause();
285
- }
286
- AppModel.ofEvent(context).hideActions(context);
287
- Navigator.of(context).push(
288
- createSlideRoute(
289
- context: context,
290
- slideDir: dir,
291
- page: ChapterViewScreen(book: book, chapter: chapter),
292
- ),
293
- );
294
- }
295
-
296
- onNext(BuildContext context, int book, int chapter) {
297
- final selectedBible = AppModel.ofEvent(context).bible;
298
- final selectedBook = selectedBible.books[book];
299
- if (selectedBook.chapters.length > chapter + 1) {
300
- navigateBookChapter(context, selectedBook.index, chapter + 1, TextDirection.ltr);
301
- } else {
302
- if (selectedBook.index + 1 < selectedBible.books.length) {
303
- final nextBook = selectedBible.books[selectedBook.index + 1];
304
- navigateBookChapter(context, nextBook.index, 0, TextDirection.ltr);
305
- }
306
- }
307
- }
308
-
309
- onPrevious(BuildContext context, int book, int chapter) {
310
- final selectedBible = AppModel.ofEvent(context).bible;
311
- final selectedBook = selectedBible.books[book];
312
- if (chapter - 1 >= 0) {
313
- // if (Navigator.of(context).canPop()) {
314
- // Navigator.of(context).pop();
315
- // } else {
316
- navigateBookChapter(context, selectedBook.index, chapter - 1, TextDirection.rtl);
317
- // }
318
- } else {
319
- if (selectedBook.index - 1 >= 0) {
320
- final prevBook = selectedBible.books[selectedBook.index - 1];
321
- navigateBookChapter(context, prevBook.index, prevBook.chapters.length - 1, TextDirection.rtl);
322
- }
323
- }
324
- }
325
-
326
- bool hasSelectedVerses() {
327
- return selectedVerses.isNotEmpty;
328
- }
329
-
330
- void clearSelections(BuildContext context) {
331
- selectedVerses.clear();
332
- AppModel.ofEvent(context).hideActions(context);
333
- notifyListeners();
334
- }
335
-
336
- bool isVerseSelected(Verse v) {
337
- return selectedVerses.any((el) => el.index == v.index);
338
- }
339
-
340
- bool isVerseHighlighted(BuildContext context) {
341
- // box.read("${book}:${chapter}:${verse}", "color");
342
- return false;
343
- }
344
-
345
- void onVerseSelected(BuildContext context, Verse v) {
346
- if (selectedVerses.isEmpty) {
347
- AppModel.ofEvent(context).showActions(context);
348
- }
349
- if (isVerseSelected(v)) {
350
- selectedVerses.removeWhere((it) => it.index == v.index);
351
- } else {
352
- selectedVerses.add(v);
353
- }
354
- if (selectedVerses.isEmpty) {
355
- AppModel.ofEvent(context).hideActions(context);
356
- }
357
- notifyListeners();
358
- }
359
-
360
- void copyVerses() {
361
- final text = selectedVerses.map((e) => e.text).join("\n");
362
- Clipboard.setData(ClipboardData(text: text));
363
- // maybe close the action menu or show a snackbar
364
- // ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Email address copied to clipboard")));
365
- }
366
-
367
- void shareVerses(BuildContext context) {
368
- final bible = AppModel.ofEvent(context).bible;
369
- final name = bible.books[selectedVerses.first.book].name;
370
- final chapter = selectedVerses.first.chapter + 1;
371
- final title = "$name $chapter: ${selectedVerses.map((e) => e.index + 1).join(", ")}";
372
- final text = selectedVerses.map((e) => e.text).join("\n");
373
- Share.share("$title\n$text", subject: title);
374
- }
375
-
376
- pause() async {
377
- await player.pause();
378
- isPlaying = false;
379
- notifyListeners();
380
- }
381
-
382
- onPlay(BuildContext context) async {
383
- final bible = AppModel.ofEvent(context).bible;
384
- if (!bible.hasAudio) {
385
- showError(
386
- context,
387
- "This Bible doesn't support audio. Currently audio is only available for the Kannada Bible.",
388
- );
389
- return;
390
- }
391
- if (isPlaying) {
392
- pause();
393
- } else {
394
- isPlaying = true;
395
- notifyListeners();
396
- for (final v in selectedVerses) {
397
- final bibleName = bible.name;
398
- final book = (v.book + 1).toString().padLeft(2, "0");
399
- final chapter = (v.chapter + 1).toString().padLeft(3, "0");
400
- final verseNo = (v.index + 1).toString().padLeft(3, "0");
401
- final pathname = "$bibleName/$book-$chapter-$verseNo.mp3";
402
- try {
403
- final url = await FirebaseStorage.instance.ref(pathname).getDownloadURL();
404
- await player.setUrl(url);
405
- await player.play();
406
- await player.stop();
407
- } catch (err) {
408
- log("Could not play audio", name: "play", error: (err.toString(), pathname));
409
- FirebaseCrashlytics.instance.recordFlutterError(FlutterErrorDetails(exception: (err.toString(), pathname)));
410
- showError(context, "Could not play audio");
411
- return;
412
- } finally {
413
- pause();
414
- }
415
- }
416
- }
417
- }
418
- }
419
-
420
- bool isDesktop() {
421
- return defaultTargetPlatform == TargetPlatform.macOS ||
422
- defaultTargetPlatform == TargetPlatform.windows ||
423
- defaultTargetPlatform == TargetPlatform.linux;
424
- }
425
-
426
- bool isIOS() {
427
- return defaultTargetPlatform == TargetPlatform.iOS;
428
- }
429
-
430
- bool isWide(BuildContext context) {
431
- if (defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS) {
432
- return false;
433
- }
434
- final width = MediaQuery.of(context).size.width;
435
- return width > 600;
436
- }
437
-
438
- createNoTransitionPageRoute(Widget page) {
439
- return PageRouteBuilder(
440
- opaque: false,
441
- transitionDuration: Duration.zero,
442
- reverseTransitionDuration: Duration.zero,
443
- pageBuilder: (context, _, __) => page,
444
- );
445
- }
446
-
447
- createSlideRoute({required BuildContext context, TextDirection? slideDir, required Widget page}) {
448
- if (isWide(context) || slideDir == null) {
449
- return PageRouteBuilder(
450
- pageBuilder: (context, _, __) {
451
- return page;
452
- },
453
- );
454
- }
455
- return PageRouteBuilder(
456
- pageBuilder: (context, animation, secondaryAnimation) => page,
457
- transitionsBuilder: (context, animation, secondaryAnimation, child) {
458
- const begin = Offset(1.0, 0.0);
459
- const end = Offset.zero;
460
- const curve = Curves.ease;
461
- var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
462
- return SlideTransition(
463
- textDirection: slideDir,
464
- position: animation.drive(tween),
465
- child: child,
466
- );
467
- },
468
- );
469
- }
470
-
471
- getBibleFromAsset(String languageCode, String file) async {
472
- final bytes = await rootBundle.load("assets/bibles/$file.txt");
473
- return getBibleFromText(languageCode, utf8.decode(bytes.buffer.asUint8List(), allowMalformed: false));
474
- }
lib/utils.dart ADDED
@@ -0,0 +1,62 @@
1
+ import "dart:convert";
2
+ import "package:flutter/foundation.dart" show defaultTargetPlatform, TargetPlatform;
3
+ import "package:flutter/material.dart";
4
+ import "package:flutter/services.dart";
5
+ import "package:only_bible_app/models.dart";
6
+
7
+
8
+ bool isDesktop() {
9
+ return defaultTargetPlatform == TargetPlatform.macOS ||
10
+ defaultTargetPlatform == TargetPlatform.windows ||
11
+ defaultTargetPlatform == TargetPlatform.linux;
12
+ }
13
+
14
+ bool isIOS() {
15
+ return defaultTargetPlatform == TargetPlatform.iOS;
16
+ }
17
+
18
+ bool isWide(BuildContext context) {
19
+ if (defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS) {
20
+ return false;
21
+ }
22
+ final width = MediaQuery.of(context).size.width;
23
+ return width > 600;
24
+ }
25
+
26
+ createNoTransitionPageRoute(Widget page) {
27
+ return PageRouteBuilder(
28
+ opaque: false,
29
+ transitionDuration: Duration.zero,
30
+ reverseTransitionDuration: Duration.zero,
31
+ pageBuilder: (context, _, __) => page,
32
+ );
33
+ }
34
+
35
+ createSlideRoute({required BuildContext context, TextDirection? slideDir, required Widget page}) {
36
+ if (isWide(context) || slideDir == null) {
37
+ return PageRouteBuilder(
38
+ pageBuilder: (context, _, __) {
39
+ return page;
40
+ },
41
+ );
42
+ }
43
+ return PageRouteBuilder(
44
+ pageBuilder: (context, animation, secondaryAnimation) => page,
45
+ transitionsBuilder: (context, animation, secondaryAnimation, child) {
46
+ const begin = Offset(1.0, 0.0);
47
+ const end = Offset.zero;
48
+ const curve = Curves.ease;
49
+ var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
50
+ return SlideTransition(
51
+ textDirection: slideDir,
52
+ position: animation.drive(tween),
53
+ child: child,
54
+ );
55
+ },
56
+ );
57
+ }
58
+
59
+ getBibleFromAsset(String languageCode, String file) async {
60
+ final bytes = await rootBundle.load("assets/bibles/$file.txt");
61
+ return getBibleFromText(languageCode, utf8.decode(bytes.buffer.asUint8List(), allowMalformed: false));
62
+ }
lib/widgets/actions_sheet.dart CHANGED
@@ -1,5 +1,7 @@
1
1
  import "package:flutter/material.dart";
2
+ import "package:only_bible_app/providers/app_model.dart";
3
+ import "package:only_bible_app/providers/chapter_view_model.dart";
2
- import "package:only_bible_app/state.dart";
4
+ import "package:only_bible_app/utils.dart";
3
5
  import "package:only_bible_app/widgets/highlight_button.dart";
4
6
  import "package:only_bible_app/widgets/icon_button_text.dart";
5
7
 
lib/widgets/chapter_app_bar.dart CHANGED
@@ -1,5 +1,7 @@
1
1
  import "package:flutter/material.dart";
2
+ import "package:only_bible_app/providers/app_model.dart";
3
+ import "package:only_bible_app/providers/chapter_view_model.dart";
2
- import "package:only_bible_app/state.dart";
4
+ import "package:only_bible_app/utils.dart";
3
5
 
4
6
  class ChapterAppBar extends StatelessWidget implements PreferredSizeWidget {
5
7
  const ChapterAppBar({super.key});
lib/widgets/note_sheet.dart CHANGED
@@ -1,6 +1,6 @@
1
1
  import "package:flutter/material.dart";
2
2
  import "package:only_bible_app/models.dart";
3
- import "package:only_bible_app/state.dart";
3
+ import "package:only_bible_app/providers/app_model.dart";
4
4
  import "package:only_bible_app/widgets/modal_button.dart";
5
5
 
6
6
  class NoteSheet extends StatelessWidget {
lib/widgets/scaffold_menu.dart CHANGED
@@ -1,5 +1,5 @@
1
1
  import "package:flutter/material.dart";
2
- import "package:only_bible_app/state.dart";
2
+ import "package:only_bible_app/utils.dart";
3
3
 
4
4
  class ScaffoldMenu extends StatelessWidget {
5
5
  final Widget child;
lib/widgets/settings_sheet.dart CHANGED
@@ -1,5 +1,5 @@
1
1
  import "package:flutter/material.dart";
2
- import "package:only_bible_app/state.dart";
2
+ import "package:only_bible_app/providers/app_model.dart";
3
3
  import "package:settings_ui/settings_ui.dart";
4
4
 
5
5
  class SettingsSheet extends StatelessWidget {
lib/widgets/sliver_tile_grid.dart CHANGED
@@ -1,5 +1,5 @@
1
1
  import "package:flutter/material.dart";
2
- import "package:only_bible_app/state.dart";
2
+ import "package:only_bible_app/utils.dart";
3
3
 
4
4
  enum ListType {
5
5
  small,
lib/widgets/verses_view.dart CHANGED
@@ -1,7 +1,8 @@
1
1
  import "package:flutter/gestures.dart";
2
2
  import "package:flutter/material.dart";
3
3
  import "package:flutter_swipe_detector/flutter_swipe_detector.dart";
4
- import "package:only_bible_app/state.dart";
4
+ import "package:only_bible_app/providers/app_model.dart";
5
+ import "package:only_bible_app/providers/chapter_view_model.dart";
5
6
  import "package:provider/provider.dart";
6
7
 
7
8
  class VersesView extends StatelessWidget {
pubspec.lock CHANGED
@@ -257,70 +257,6 @@ packages:
257
257
  url: "https://pub.dev"
258
258
  source: hosted
259
259
  version: "6.1.4"
260
- file_selector:
261
- dependency: "direct main"
262
- description:
263
- name: file_selector
264
- sha256: "59b35aa4af7988be7ec88f9ddaa6c71c5b54bf0f8b35009389d9343b10e9c3af"
265
- url: "https://pub.dev"
266
- source: hosted
267
- version: "1.0.0"
268
- file_selector_android:
269
- dependency: transitive
270
- description:
271
- name: file_selector_android
272
- sha256: "43e5c719f671b9181bef1bf2851135c3ad993a9a6c804a4ccb07579cfee84e34"
273
- url: "https://pub.dev"
274
- source: hosted
275
- version: "0.5.0+2"
276
- file_selector_ios:
277
- dependency: transitive
278
- description:
279
- name: file_selector_ios
280
- sha256: "507af301b21b1dbb6fd0615ba21190b2b4574edb672929f32ce7f610c40a9bd9"
281
- url: "https://pub.dev"
282
- source: hosted
283
- version: "0.5.1+5"
284
- file_selector_linux:
285
- dependency: transitive
286
- description:
287
- name: file_selector_linux
288
- sha256: "770eb1ab057b5ae4326d1c24cc57710758b9a46026349d021d6311bd27580046"
289
- url: "https://pub.dev"
290
- source: hosted
291
- version: "0.9.2"
292
- file_selector_macos:
293
- dependency: transitive
294
- description:
295
- name: file_selector_macos
296
- sha256: "4ada532862917bf16e3adb3891fe3a5917a58bae03293e497082203a80909412"
297
- url: "https://pub.dev"
298
- source: hosted
299
- version: "0.9.3+1"
300
- file_selector_platform_interface:
301
- dependency: transitive
302
- description:
303
- name: file_selector_platform_interface
304
- sha256: "412705a646a0ae90f33f37acfae6a0f7cbc02222d6cd34e479421c3e74d3853c"
305
- url: "https://pub.dev"
306
- source: hosted
307
- version: "2.6.0"
308
- file_selector_web:
309
- dependency: transitive
310
- description:
311
- name: file_selector_web
312
- sha256: e292740c469df0aeeaba0895bf622bea351a05e87d22864c826bf21c4780e1d7
313
- url: "https://pub.dev"
314
- source: hosted
315
- version: "0.9.2"
316
- file_selector_windows:
317
- dependency: transitive
318
- description:
319
- name: file_selector_windows
320
- sha256: "1372760c6b389842b77156203308940558a2817360154084368608413835fc26"
321
- url: "https://pub.dev"
322
- source: hosted
323
- version: "0.9.3"
324
260
  firebase_core:
325
261
  dependency: "direct main"
326
262
  description:
pubspec.yaml CHANGED
@@ -26,7 +26,6 @@ dependencies:
26
26
  settings_ui: ^2.0.2
27
27
  get_storage: ^2.1.1
28
28
  share_plus: ^7.1.0
29
- file_selector: ^1.0.0 # need this to fix android build
30
29
 
31
30
  dev_dependencies:
32
31
  flutter_test: