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