~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.
cf00613a
—
pyrossh 2 years ago
fix navigation
- lib/app.dart +18 -9
- lib/main.dart +1 -0
- lib/models.dart +1 -1
- lib/screens/chapter_select_screen.dart +6 -1
- lib/screens/chapter_view_screen.dart +37 -42
- lib/state.dart +99 -84
- lib/widgets/header.dart +76 -75
- lib/widgets/play_button.dart +3 -2
- lib/widgets/sliver_tile_grid.dart +6 -4
- lib/widgets/verse_list.dart +4 -2
- lib/widgets/verse_view.dart +2 -2
- pubspec.lock +16 -0
- pubspec.yaml +1 -0
lib/app.dart
CHANGED
|
@@ -11,14 +11,23 @@ class App extends StatelessWidget {
|
|
|
11
11
|
@override
|
|
12
12
|
Widget build(BuildContext context) {
|
|
13
13
|
return MaterialApp(
|
|
14
|
-
|
|
14
|
+
title: "Only Bible App",
|
|
15
|
-
|
|
15
|
+
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
16
|
-
|
|
16
|
+
supportedLocales: AppLocalizations.supportedLocales,
|
|
17
|
-
|
|
17
|
+
debugShowCheckedModeBanner: false,
|
|
18
|
-
|
|
18
|
+
themeMode: darkMode.reactiveValue(context) ? ThemeMode.dark : ThemeMode.light,
|
|
19
|
-
|
|
19
|
+
theme: lightTheme,
|
|
20
|
-
|
|
20
|
+
darkTheme: darkTheme,
|
|
21
|
+
home: FutureBuilder(
|
|
22
|
+
future: loadPrefs(),
|
|
23
|
+
builder: (context, snapshot) {
|
|
24
|
+
if (snapshot.connectionState == ConnectionState.done) {
|
|
21
|
-
|
|
25
|
+
return ChapterViewScreen(book: snapshot.data!.$1, chapter: snapshot.data!.$2);
|
|
26
|
+
}
|
|
27
|
+
return const Center(
|
|
28
|
+
child: CircularProgressIndicator(),
|
|
22
|
-
|
|
29
|
+
);
|
|
30
|
+
},
|
|
31
|
+
));
|
|
23
32
|
}
|
|
24
33
|
}
|
lib/main.dart
CHANGED
|
@@ -30,6 +30,7 @@ void main() async {
|
|
|
30
30
|
// };
|
|
31
31
|
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
|
|
32
32
|
await initPersistentValueNotifier();
|
|
33
|
+
await loadPrefs();
|
|
33
34
|
await loadBible();
|
|
34
35
|
await updateStatusBar();
|
|
35
36
|
runApp(const App());
|
lib/models.dart
CHANGED
|
@@ -188,4 +188,4 @@ List<Book> getBibleFromText(String text) {
|
|
|
188
188
|
);
|
|
189
189
|
}
|
|
190
190
|
return books;
|
|
191
|
-
}
|
|
191
|
+
}
|
lib/screens/chapter_select_screen.dart
CHANGED
|
@@ -3,6 +3,7 @@ import "package:only_bible_app/state.dart";
|
|
|
3
3
|
import "package:only_bible_app/widgets/scaffold_menu.dart";
|
|
4
4
|
import "package:only_bible_app/widgets/sliver_tile_grid.dart";
|
|
5
5
|
import "package:only_bible_app/widgets/sliver_heading.dart";
|
|
6
|
+
import "package:only_bible_app/screens/chapter_view_screen.dart";
|
|
6
7
|
|
|
7
8
|
class ChapterSelectScreen extends StatelessWidget {
|
|
8
9
|
final int selectedBookIndex;
|
|
@@ -10,7 +11,11 @@ class ChapterSelectScreen extends StatelessWidget {
|
|
|
10
11
|
const ChapterSelectScreen({super.key, required this.selectedBookIndex});
|
|
11
12
|
|
|
12
13
|
onChapterSelected(BuildContext context, int index) {
|
|
14
|
+
Navigator.of(context).pushReplacement(
|
|
15
|
+
createNoTransitionPageRoute(
|
|
13
|
-
|
|
16
|
+
ChapterViewScreen(book: selectedBookIndex, chapter: index),
|
|
17
|
+
),
|
|
18
|
+
);
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
@override
|
lib/screens/chapter_view_screen.dart
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import "package:flutter/material.dart";
|
|
2
2
|
import "package:flutter_reactive_value/flutter_reactive_value.dart";
|
|
3
3
|
import "package:flutter_swipe_detector/flutter_swipe_detector.dart";
|
|
4
|
+
import "package:only_bible_app/widgets/actions_bar.dart";
|
|
4
5
|
import "package:only_bible_app/widgets/header.dart";
|
|
5
6
|
import "package:only_bible_app/state.dart";
|
|
6
|
-
import "package:only_bible_app/widgets/play_button.dart";
|
|
7
7
|
import "package:only_bible_app/widgets/sidebar.dart";
|
|
8
8
|
import "package:only_bible_app/widgets/verse_list.dart";
|
|
9
|
+
import "package:provider/provider.dart";
|
|
9
10
|
|
|
10
11
|
class ChapterViewScreen extends StatelessWidget {
|
|
11
12
|
final int book;
|
|
@@ -15,48 +16,42 @@ class ChapterViewScreen extends StatelessWidget {
|
|
|
15
16
|
|
|
16
17
|
@override
|
|
17
18
|
Widget build(BuildContext context) {
|
|
19
|
+
return ChangeNotifierProvider(
|
|
18
|
-
|
|
20
|
+
create: (context) => ChapterViewModel(
|
|
21
|
+
book: book,
|
|
22
|
+
chapter: chapter,
|
|
19
|
-
|
|
23
|
+
selectedVerses: [],
|
|
24
|
+
),
|
|
20
|
-
|
|
25
|
+
child: Scaffold(
|
|
21
|
-
|
|
26
|
+
backgroundColor: Theme.of(context).colorScheme.background,
|
|
22
|
-
|
|
27
|
+
bottomSheet: const ActionsBar(),
|
|
23
|
-
|
|
28
|
+
body: SafeArea(
|
|
29
|
+
child: SwipeDetector(
|
|
24
|
-
|
|
30
|
+
onSwipeLeft: (offset) {
|
|
31
|
+
onNext(context, book, chapter);
|
|
32
|
+
},
|
|
25
|
-
|
|
33
|
+
onSwipeRight: (offset) {
|
|
26
|
-
|
|
34
|
+
onPrevious(context, book, chapter);
|
|
27
|
-
// TODO: check if this is needed
|
|
28
|
-
|
|
35
|
+
},
|
|
29
|
-
|
|
36
|
+
child: Row(
|
|
30
|
-
mainAxisAlignment: MainAxisAlignment.center,
|
|
31
|
-
|
|
37
|
+
children: [
|
|
38
|
+
if (isWide(context)) const Sidebar(),
|
|
39
|
+
const Flexible(
|
|
40
|
+
child: Column(
|
|
41
|
+
children: [
|
|
32
|
-
|
|
42
|
+
Header(),
|
|
43
|
+
Flexible(
|
|
44
|
+
child: VerseList(),
|
|
45
|
+
),
|
|
46
|
+
// TODO: add padding only if bottom sheet is shown
|
|
47
|
+
// Padding(
|
|
48
|
+
// padding: EdgeInsets.only(bottom: 40),
|
|
49
|
+
// )
|
|
33
|
-
|
|
50
|
+
],
|
|
34
|
-
|
|
51
|
+
),
|
|
35
|
-
),
|
|
36
|
-
)
|
|
37
|
-
: null,
|
|
38
|
-
body: SafeArea(
|
|
39
|
-
child: SwipeDetector(
|
|
40
|
-
onSwipeLeft: (offset) {
|
|
41
|
-
onNext(context);
|
|
42
|
-
},
|
|
43
|
-
onSwipeRight: (offset) {
|
|
44
|
-
onPrevious(context);
|
|
45
|
-
},
|
|
46
|
-
child: Row(
|
|
47
|
-
children: [
|
|
48
|
-
if (isWide(context)) const Sidebar(),
|
|
49
|
-
const Flexible(
|
|
50
|
-
child: Column(
|
|
51
|
-
children: [
|
|
52
|
-
Header(),
|
|
53
|
-
Flexible(
|
|
54
|
-
child: VerseList(),
|
|
55
|
-
),
|
|
56
|
-
],
|
|
57
52
|
),
|
|
53
|
+
],
|
|
58
|
-
|
|
54
|
+
),
|
|
59
|
-
],
|
|
60
55
|
),
|
|
61
56
|
),
|
|
62
57
|
),
|
lib/state.dart
CHANGED
|
@@ -3,14 +3,91 @@ import "package:flutter/foundation.dart" show defaultTargetPlatform, TargetPlatf
|
|
|
3
3
|
import "package:flutter/services.dart";
|
|
4
4
|
import "package:flutter/material.dart";
|
|
5
5
|
import "package:flutter_persistent_value_notifier/flutter_persistent_value_notifier.dart";
|
|
6
|
-
import "package:flutter_reactive_value/flutter_reactive_value.dart";
|
|
7
6
|
import "package:just_audio/just_audio.dart";
|
|
8
7
|
import "package:only_bible_app/screens/chapter_view_screen.dart";
|
|
9
8
|
import "package:only_bible_app/utils/dialog.dart";
|
|
10
9
|
import "package:only_bible_app/models.dart";
|
|
10
|
+
import "package:provider/provider.dart";
|
|
11
|
+
import "package:shared_preferences/shared_preferences.dart";
|
|
11
12
|
|
|
13
|
+
class ChapterViewModel extends ChangeNotifier {
|
|
14
|
+
final int book;
|
|
15
|
+
final int chapter;
|
|
16
|
+
final List<int> selectedVerses;
|
|
17
|
+
final player = AudioPlayer();
|
|
18
|
+
|
|
19
|
+
static ChapterViewModel of(BuildContext context) {
|
|
20
|
+
return Provider.of<ChapterViewModel>(context, listen: true);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static ChapterViewModel ofEvent(BuildContext context) {
|
|
24
|
+
return Provider.of<ChapterViewModel>(context, listen: false);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
ChapterViewModel({required this.book, required this.chapter, required this.selectedVerses}) {
|
|
28
|
+
savePrefs(book, chapter);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
bool hasSelectedVerses() {
|
|
32
|
+
return selectedVerses.isNotEmpty;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
bool isVerseSelected(int i) {
|
|
36
|
+
return selectedVerses.contains(i);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
void onVerseSelected(int i) {
|
|
40
|
+
if (selectedVerses.contains(i)) {
|
|
41
|
+
selectedVerses.remove(i);
|
|
42
|
+
} else {
|
|
43
|
+
selectedVerses.add(i);
|
|
44
|
+
}
|
|
45
|
+
notifyListeners();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
onPlay(BuildContext context) async {
|
|
12
|
-
final
|
|
49
|
+
final model = ChapterViewModel.ofEvent(context);
|
|
50
|
+
if (isPlaying.value) {
|
|
51
|
+
await player.pause();
|
|
52
|
+
isPlaying.value = false;
|
|
53
|
+
} else {
|
|
54
|
+
try {
|
|
55
|
+
isPlaying.value = true;
|
|
56
|
+
for (final v in selectedVerses) {
|
|
57
|
+
final bibleName = selectedBible.value!.name;
|
|
58
|
+
final book = (model.book + 1).toString().padLeft(2, "0");
|
|
59
|
+
final chapter = (model.chapter + 1).toString().padLeft(3, "0");
|
|
60
|
+
final verse = (v + 1).toString().padLeft(3, "0");
|
|
61
|
+
await player.setUrl(
|
|
62
|
+
"http://localhost:3000/$bibleName/$book-$chapter-$verse.mp3",
|
|
63
|
+
);
|
|
64
|
+
await player.play();
|
|
65
|
+
await player.stop();
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
// TODO: log this error
|
|
69
|
+
print(err.toString());
|
|
70
|
+
showError(context, "Could not play audio");
|
|
71
|
+
} finally {
|
|
72
|
+
await player.pause();
|
|
73
|
+
isPlaying.value = false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
Future<(int, int)> loadPrefs() async {
|
|
13
|
-
final
|
|
80
|
+
final prefs = await SharedPreferences.getInstance();
|
|
81
|
+
// 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);
|
|
90
|
+
}
|
|
14
91
|
|
|
15
92
|
final darkMode = PersistentValueNotifier<bool>(
|
|
16
93
|
sharedPreferencesKey: "darkMode",
|
|
@@ -27,19 +104,7 @@ final selectedBibleId = PersistentValueNotifier(
|
|
|
27
104
|
initialValue: 1,
|
|
28
105
|
);
|
|
29
106
|
|
|
30
|
-
final bookIndex = PersistentValueNotifier<int>(
|
|
31
|
-
sharedPreferencesKey: "bookIndex",
|
|
32
|
-
initialValue: 0,
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
final chapterIndex = PersistentValueNotifier<int>(
|
|
36
|
-
sharedPreferencesKey: "chapterIndex",
|
|
37
|
-
initialValue: 0,
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
final slideTextDir = ValueNotifier<TextDirection?>(null);
|
|
41
107
|
final selectedBible = ValueNotifier<Bible?>(null);
|
|
42
|
-
final selectedVerses = ValueNotifier([]);
|
|
43
108
|
final isPlaying = ValueNotifier(false);
|
|
44
109
|
final fontSizeDelta = ValueNotifier(0);
|
|
45
110
|
|
|
@@ -88,11 +153,19 @@ updateStatusBar() {
|
|
|
88
153
|
|
|
89
154
|
changeBible(BuildContext context, int i) {
|
|
90
155
|
selectedBibleId.value = i;
|
|
156
|
+
// TODO: maybe use a future as the bible needs to load
|
|
91
157
|
loadBible();
|
|
92
|
-
// This page is invoked with the other navigator so can't use context.pop()
|
|
93
158
|
Navigator.of(context).pop();
|
|
94
159
|
}
|
|
95
160
|
|
|
161
|
+
createNoTransitionPageRoute(Widget page) {
|
|
162
|
+
return PageRouteBuilder(
|
|
163
|
+
pageBuilder: (context, _, __) {
|
|
164
|
+
return page;
|
|
165
|
+
},
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
96
169
|
createSlideRoute({required BuildContext context, TextDirection? slideDir, required Widget page}) {
|
|
97
170
|
if (isWide(context) || slideDir == null) {
|
|
98
171
|
return PageRouteBuilder(
|
|
@@ -117,53 +190,38 @@ createSlideRoute({required BuildContext context, TextDirection? slideDir, requir
|
|
|
117
190
|
);
|
|
118
191
|
}
|
|
119
192
|
|
|
120
|
-
navigateBookChapter(BuildContext context, int book, int chapter,
|
|
193
|
+
navigateBookChapter(BuildContext context, int book, int chapter, TextDirection? dir) {
|
|
121
|
-
print("${bookIndex.value} ${book} ${chapterIndex.value} ${chapter}");
|
|
122
|
-
var slideDir = TextDirection.ltr;
|
|
123
|
-
if (book > bookIndex.value) {
|
|
124
|
-
slideDir = TextDirection.ltr;
|
|
125
|
-
} else if (bookIndex.value > book) {
|
|
126
|
-
slideDir = TextDirection.rtl;
|
|
127
|
-
} else if (chapterIndex.value > chapter) {
|
|
128
|
-
slideDir = TextDirection.rtl;
|
|
129
|
-
}
|
|
130
|
-
// final slideDir = book > bookIndex.value || ? TextDirection.rtl : TextDirection.ltr;
|
|
131
194
|
// TODO: add bible param here maybe
|
|
132
195
|
// route: /bible/book/chapter
|
|
133
|
-
bookIndex.value = book;
|
|
134
|
-
chapterIndex.value = chapter;
|
|
135
|
-
selectedVerses.value.clear();
|
|
136
196
|
Navigator.of(context).push(
|
|
137
197
|
createSlideRoute(
|
|
138
198
|
context: context,
|
|
139
|
-
slideDir:
|
|
199
|
+
slideDir: dir,
|
|
140
200
|
page: ChapterViewScreen(book: book, chapter: chapter),
|
|
141
201
|
),
|
|
142
202
|
);
|
|
143
203
|
}
|
|
144
204
|
|
|
145
|
-
onNext(BuildContext context) {
|
|
205
|
+
onNext(BuildContext context, int book, int chapter) {
|
|
146
|
-
final selectedBook = selectedBible.value!.books[
|
|
206
|
+
final selectedBook = selectedBible.value!.books[book];
|
|
147
|
-
final chapter = chapterIndex.value;
|
|
148
207
|
if (selectedBook.chapters.length > chapter + 1) {
|
|
149
|
-
navigateBookChapter(context, selectedBook.index, chapter + 1,
|
|
208
|
+
navigateBookChapter(context, selectedBook.index, chapter + 1, TextDirection.ltr);
|
|
150
209
|
} else {
|
|
151
210
|
if (selectedBook.index + 1 < selectedBible.value!.books.length) {
|
|
152
211
|
final nextBook = selectedBible.value!.books[selectedBook.index + 1];
|
|
153
|
-
navigateBookChapter(context, nextBook.index, 0,
|
|
212
|
+
navigateBookChapter(context, nextBook.index, 0, TextDirection.ltr);
|
|
154
213
|
}
|
|
155
214
|
}
|
|
156
215
|
}
|
|
157
216
|
|
|
158
|
-
onPrevious(BuildContext context) {
|
|
217
|
+
onPrevious(BuildContext context, int book, int chapter) {
|
|
159
|
-
final selectedBook = selectedBible.value!.books[
|
|
218
|
+
final selectedBook = selectedBible.value!.books[book];
|
|
160
|
-
final chapter = chapterIndex.value;
|
|
161
219
|
if (chapter - 1 >= 0) {
|
|
162
|
-
navigateBookChapter(context, selectedBook.index, chapter - 1,
|
|
220
|
+
navigateBookChapter(context, selectedBook.index, chapter - 1, TextDirection.rtl);
|
|
163
221
|
} else {
|
|
164
222
|
if (selectedBook.index - 1 >= 0) {
|
|
165
223
|
final prevBook = selectedBible.value!.books[selectedBook.index - 1];
|
|
166
|
-
navigateBookChapter(context, prevBook.index, prevBook.chapters.length - 1,
|
|
224
|
+
navigateBookChapter(context, prevBook.index, prevBook.chapters.length - 1, TextDirection.rtl);
|
|
167
225
|
}
|
|
168
226
|
}
|
|
169
227
|
}
|
|
@@ -182,46 +240,3 @@ getBibleFromAsset(String file) async {
|
|
|
182
240
|
final bytes = await rootBundle.load("assets/bibles/$file.txt");
|
|
183
241
|
return getBibleFromText(utf8.decode(bytes.buffer.asUint8List(), allowMalformed: false));
|
|
184
242
|
}
|
|
185
|
-
|
|
186
|
-
final player = AudioPlayer();
|
|
187
|
-
|
|
188
|
-
onPlay(BuildContext context) async {
|
|
189
|
-
if (isPlaying.value) {
|
|
190
|
-
await player.pause();
|
|
191
|
-
isPlaying.value = false;
|
|
192
|
-
} else {
|
|
193
|
-
try {
|
|
194
|
-
isPlaying.value = true;
|
|
195
|
-
for (final v in selectedVerses.value) {
|
|
196
|
-
final bibleName = selectedBible.value!.name;
|
|
197
|
-
final book = (bookIndex.value + 1).toString().padLeft(2, "0");
|
|
198
|
-
final chapter = (chapterIndex.value + 1).toString().padLeft(3, "0");
|
|
199
|
-
final verse = (v + 1).toString().padLeft(3, "0");
|
|
200
|
-
await player.setUrl(
|
|
201
|
-
"http://localhost:3000/$bibleName/$book-$chapter-$verse.mp3",
|
|
202
|
-
);
|
|
203
|
-
await player.play();
|
|
204
|
-
await player.stop();
|
|
205
|
-
}
|
|
206
|
-
} catch (err) {
|
|
207
|
-
// TODO: log this error
|
|
208
|
-
print(err.toString());
|
|
209
|
-
showError(context, "Could not play audio");
|
|
210
|
-
} finally {
|
|
211
|
-
await player.pause();
|
|
212
|
-
isPlaying.value = false;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
isVerseSelected(BuildContext context, int i) {
|
|
218
|
-
return selectedVerses.reactiveValue(context).contains(i);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
onVerseSelected(int i) {
|
|
222
|
-
if (selectedVerses.value.contains(i)) {
|
|
223
|
-
selectedVerses.value = [...selectedVerses.value.where((it) => it != i)];
|
|
224
|
-
} else {
|
|
225
|
-
selectedVerses.value = [...selectedVerses.value, i];
|
|
226
|
-
}
|
|
227
|
-
}
|
lib/widgets/header.dart
CHANGED
|
@@ -11,86 +11,87 @@ class Header extends StatelessWidget {
|
|
|
11
11
|
|
|
12
12
|
@override
|
|
13
13
|
Widget build(BuildContext context) {
|
|
14
|
-
final book =
|
|
14
|
+
final book = ChapterViewModel.of(context).book;
|
|
15
|
-
final chapter =
|
|
15
|
+
final chapter = ChapterViewModel.of(context).chapter;
|
|
16
16
|
final selectedBook = selectedBible.value!.books[book];
|
|
17
17
|
final isDesktop = isWide(context);
|
|
18
18
|
return Container(
|
|
19
|
-
|
|
19
|
+
padding: EdgeInsets.only(
|
|
20
|
-
|
|
20
|
+
left: 20,
|
|
21
|
-
|
|
21
|
+
right: 20,
|
|
22
|
-
|
|
22
|
+
top: isWide(context) ? 10 : 0,
|
|
23
|
-
|
|
23
|
+
bottom: 0,
|
|
24
|
-
|
|
24
|
+
),
|
|
25
|
-
|
|
25
|
+
child: Column(
|
|
26
|
-
|
|
26
|
+
children: [
|
|
27
|
-
|
|
27
|
+
Row(
|
|
28
|
-
|
|
28
|
+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
29
|
-
|
|
29
|
+
crossAxisAlignment: CrossAxisAlignment.center,
|
|
30
|
-
|
|
30
|
+
children: [
|
|
31
|
-
|
|
31
|
+
TextButton.icon(
|
|
32
|
-
|
|
32
|
+
style: TextButton.styleFrom(
|
|
33
|
-
|
|
33
|
+
padding: EdgeInsets.zero,
|
|
34
|
-
|
|
34
|
+
backgroundColor: Theme.of(context).colorScheme.background,
|
|
35
|
-
|
|
35
|
+
elevation: 0,
|
|
36
|
-
|
|
36
|
+
),
|
|
37
|
-
|
|
37
|
+
label: Icon(
|
|
38
|
-
|
|
38
|
+
Icons.expand_more,
|
|
39
|
-
|
|
39
|
+
size: 28,
|
|
40
|
-
|
|
40
|
+
color: Theme.of(context).textTheme.headlineMedium!.color,
|
|
41
|
-
|
|
41
|
+
),
|
|
42
|
-
|
|
42
|
+
icon: Text(
|
|
43
|
-
|
|
43
|
+
"${selectedBook.name} ${chapter + 1}",
|
|
44
|
-
|
|
44
|
+
style: Theme.of(context).textTheme.headlineMedium,
|
|
45
|
-
),
|
|
46
|
-
onPressed: () {
|
|
47
|
-
// TODO: move this to state
|
|
48
|
-
Navigator.of(context).push(
|
|
49
|
-
PageRouteBuilder(
|
|
50
|
-
opaque: false,
|
|
51
|
-
transitionDuration: Duration.zero,
|
|
52
|
-
reverseTransitionDuration: Duration.zero,
|
|
53
|
-
pageBuilder: (context, _, __) => const BookSelectScreen(),
|
|
54
|
-
),
|
|
55
|
-
);
|
|
56
|
-
},
|
|
57
45
|
),
|
|
46
|
+
onPressed: () {
|
|
47
|
+
// TODO: move this to state
|
|
48
|
+
Navigator.of(context).push(
|
|
49
|
+
PageRouteBuilder(
|
|
50
|
+
opaque: false,
|
|
51
|
+
transitionDuration: Duration.zero,
|
|
52
|
+
reverseTransitionDuration: Duration.zero,
|
|
53
|
+
pageBuilder: (context, _, __) => const BookSelectScreen(),
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
},
|
|
57
|
+
),
|
|
58
|
-
|
|
58
|
+
// TODO: show this in more menu in mobile
|
|
59
|
-
|
|
59
|
+
Row(
|
|
60
|
-
|
|
60
|
+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
61
|
-
|
|
61
|
+
children: [
|
|
62
|
-
|
|
62
|
+
if (isDesktop)
|
|
63
|
-
|
|
63
|
+
Container(
|
|
64
|
-
|
|
64
|
+
margin: EdgeInsets.only(right: isWide(context) ? 10 : 8),
|
|
65
|
-
|
|
65
|
+
child: TextButton(
|
|
66
|
-
|
|
66
|
+
style: TextButton.styleFrom(
|
|
67
|
-
|
|
67
|
+
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
68
|
-
),
|
|
69
|
-
child: Text(selectedBible.reactiveValue(context)!.name),
|
|
70
|
-
onPressed: () {
|
|
71
|
-
Navigator.of(context).push(
|
|
72
|
-
PageRouteBuilder(
|
|
73
|
-
opaque: false,
|
|
74
|
-
transitionDuration: Duration.zero,
|
|
75
|
-
reverseTransitionDuration: Duration.zero,
|
|
76
|
-
pageBuilder: (context, _, __) => const BibleSelectScreen(),
|
|
77
|
-
),
|
|
78
|
-
);
|
|
79
|
-
},
|
|
80
68
|
),
|
|
69
|
+
child: Text(selectedBible.reactiveValue(context)!.name),
|
|
70
|
+
onPressed: () {
|
|
71
|
+
Navigator.of(context).push(
|
|
72
|
+
PageRouteBuilder(
|
|
73
|
+
opaque: false,
|
|
74
|
+
transitionDuration: Duration.zero,
|
|
75
|
+
reverseTransitionDuration: Duration.zero,
|
|
76
|
+
pageBuilder: (context, _, __) => const BibleSelectScreen(),
|
|
77
|
+
),
|
|
78
|
+
);
|
|
79
|
+
},
|
|
81
80
|
),
|
|
81
|
+
),
|
|
82
|
-
|
|
82
|
+
if (isDesktop)
|
|
83
|
-
|
|
83
|
+
Container(
|
|
84
|
-
|
|
84
|
+
margin: EdgeInsets.only(right: isWide(context) ? 10 : 8),
|
|
85
|
-
|
|
85
|
+
child: const PlayButton(),
|
|
86
|
-
|
|
86
|
+
),
|
|
87
|
-
|
|
87
|
+
const Menu(),
|
|
88
|
-
|
|
88
|
+
],
|
|
89
|
-
|
|
89
|
+
),
|
|
90
|
-
|
|
90
|
+
],
|
|
91
|
-
|
|
91
|
+
),
|
|
92
|
-
|
|
92
|
+
Divider(height: isDesktop ? 10 : 0, endIndent: 5, thickness: 1.5),
|
|
93
|
-
|
|
93
|
+
],
|
|
94
|
+
),
|
|
94
|
-
|
|
95
|
+
);
|
|
95
96
|
}
|
|
96
97
|
}
|
lib/widgets/play_button.dart
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "package:flutter/material.dart";
|
|
2
2
|
import "package:flutter_reactive_value/flutter_reactive_value.dart";
|
|
3
|
-
import
|
|
3
|
+
import "package:only_bible_app/state.dart";
|
|
4
4
|
|
|
5
5
|
class PlayButton extends StatelessWidget {
|
|
6
6
|
const PlayButton({super.key});
|
|
@@ -9,10 +9,11 @@ class PlayButton extends StatelessWidget {
|
|
|
9
9
|
Widget build(BuildContext context) {
|
|
10
10
|
final icon = isPlaying.reactiveValue(context) ? Icons.pause_circle_filled : Icons.play_circle_fill;
|
|
11
11
|
final size = isWide(context) ? 28.0 : 42.0;
|
|
12
|
+
|
|
12
13
|
return IconButton(
|
|
13
14
|
icon: Icon(icon, size: size),
|
|
14
15
|
onPressed: () {
|
|
15
|
-
onPlay(context);
|
|
16
|
+
ChapterViewModel.ofEvent(context).onPlay(context);
|
|
16
17
|
},
|
|
17
18
|
);
|
|
18
19
|
}
|
lib/widgets/sliver_tile_grid.dart
CHANGED
|
@@ -11,19 +11,21 @@ class SliverTileGrid extends StatelessWidget {
|
|
|
11
11
|
|
|
12
12
|
@override
|
|
13
13
|
Widget build(BuildContext context) {
|
|
14
|
+
final isDesktop = isWide(context);
|
|
15
|
+
final spacing = isDesktop ? 16.0 : 12.0;
|
|
14
16
|
return SliverPadding(
|
|
15
17
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
16
18
|
sliver: SliverGrid.count(
|
|
17
19
|
crossAxisCount: listType == ListType.large
|
|
18
20
|
? 2
|
|
19
|
-
:
|
|
21
|
+
: isDesktop
|
|
20
22
|
? 6
|
|
21
23
|
: 5,
|
|
22
|
-
crossAxisSpacing:
|
|
24
|
+
crossAxisSpacing: spacing,
|
|
23
|
-
mainAxisSpacing:
|
|
25
|
+
mainAxisSpacing: spacing,
|
|
24
26
|
childAspectRatio: listType == ListType.large
|
|
25
27
|
? 4
|
|
26
|
-
:
|
|
28
|
+
: isDesktop
|
|
27
29
|
? 1.6
|
|
28
30
|
: 1.5,
|
|
29
31
|
children: children,
|
lib/widgets/verse_list.dart
CHANGED
|
@@ -8,8 +8,10 @@ class VerseList extends StatelessWidget {
|
|
|
8
8
|
|
|
9
9
|
@override
|
|
10
10
|
Widget build(BuildContext context) {
|
|
11
|
+
final book = ChapterViewModel.of(context).book;
|
|
12
|
+
final chapter = ChapterViewModel.of(context).chapter;
|
|
11
|
-
final selectedBook = selectedBible.reactiveValue(context)!.books[
|
|
13
|
+
final selectedBook = selectedBible.reactiveValue(context)!.books[book];
|
|
12
|
-
final verses = selectedBook.chapters[
|
|
14
|
+
final verses = selectedBook.chapters[chapter].verses;
|
|
13
15
|
return SelectionArea(
|
|
14
16
|
child: ListView.builder(
|
|
15
17
|
shrinkWrap: false,
|
lib/widgets/verse_view.dart
CHANGED
|
@@ -10,14 +10,14 @@ class VerseText extends StatelessWidget {
|
|
|
10
10
|
|
|
11
11
|
@override
|
|
12
12
|
Widget build(BuildContext context) {
|
|
13
|
-
final selected =
|
|
13
|
+
final selected = ChapterViewModel.of(context).isVerseSelected(index);
|
|
14
14
|
final delta = fontSizeDelta.reactiveValue(context);
|
|
15
15
|
final bodySize = Theme.of(context).textTheme.bodyMedium!.fontSize! + delta;
|
|
16
16
|
final weight =
|
|
17
17
|
fontBold.reactiveValue(context) ? FontWeight.w600 : Theme.of(context).textTheme.bodyMedium!.fontWeight;
|
|
18
18
|
|
|
19
19
|
onTap() {
|
|
20
|
-
onVerseSelected(index);
|
|
20
|
+
ChapterViewModel.ofEvent(context).onVerseSelected(index);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
return GestureDetector(
|
pubspec.lock
CHANGED
|
@@ -532,6 +532,14 @@ packages:
|
|
|
532
532
|
url: "https://pub.dev"
|
|
533
533
|
source: hosted
|
|
534
534
|
version: "1.0.4"
|
|
535
|
+
nested:
|
|
536
|
+
dependency: transitive
|
|
537
|
+
description:
|
|
538
|
+
name: nested
|
|
539
|
+
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
|
540
|
+
url: "https://pub.dev"
|
|
541
|
+
source: hosted
|
|
542
|
+
version: "1.0.0"
|
|
535
543
|
package_config:
|
|
536
544
|
dependency: transitive
|
|
537
545
|
description:
|
|
@@ -644,6 +652,14 @@ packages:
|
|
|
644
652
|
url: "https://pub.dev"
|
|
645
653
|
source: hosted
|
|
646
654
|
version: "4.2.4"
|
|
655
|
+
provider:
|
|
656
|
+
dependency: "direct main"
|
|
657
|
+
description:
|
|
658
|
+
name: provider
|
|
659
|
+
sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
|
|
660
|
+
url: "https://pub.dev"
|
|
661
|
+
source: hosted
|
|
662
|
+
version: "6.0.5"
|
|
647
663
|
pub_semver:
|
|
648
664
|
dependency: transitive
|
|
649
665
|
description:
|
pubspec.yaml
CHANGED
|
@@ -21,6 +21,7 @@ dependencies:
|
|
|
21
21
|
flutter_swipe_detector: ^2.0.0
|
|
22
22
|
cupertino_icons: ^1.0.5
|
|
23
23
|
firebase_core: ^2.15.0
|
|
24
|
+
provider: ^6.0.5
|
|
24
25
|
# firebase_crashlytics: ^3.3.4
|
|
25
26
|
# firebase_performance: ^0.9.2+4
|
|
26
27
|
|