~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.
cfa48256
—
pyrossh 2 years ago
Move state
- android/app/build.gradle +12 -2
- lib/app.dart +1 -1
- lib/main.dart +1 -1
- lib/providers/app_model.dart +235 -0
- lib/providers/chapter_view_model.dart +187 -0
- lib/screens/bible_select_screen.dart +1 -1
- lib/screens/chapter_select_screen.dart +1 -1
- lib/screens/chapter_view_screen.dart +2 -1
- lib/state.dart +0 -474
- lib/utils.dart +62 -0
- lib/widgets/actions_sheet.dart +3 -1
- lib/widgets/chapter_app_bar.dart +3 -1
- lib/widgets/note_sheet.dart +1 -1
- lib/widgets/scaffold_menu.dart +1 -1
- lib/widgets/settings_sheet.dart +1 -1
- lib/widgets/sliver_tile_grid.dart +1 -1
- lib/widgets/verses_view.dart +2 -1
- pubspec.lock +0 -64
- pubspec.yaml +0 -1
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
|
|
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-
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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:
|