~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.
65bf36fe
—
pyrossh 2 years ago
improve app overall
- README.md +1 -3
- android/app/build.gradle +2 -2
- android/build.gradle +1 -1
- ios/Podfile.lock +0 -10
- lib/app.dart +41 -42
- lib/models.dart +41 -0
- lib/routes/home_screen.dart +0 -98
- lib/screens/bible_select_screen.dart +32 -0
- lib/screens/book_select_screen.dart +55 -0
- lib/screens/chapter_select_screen.dart +35 -0
- lib/screens/chapter_view_screen.dart +64 -0
- lib/state.dart +53 -29
- lib/theme.dart +44 -1
- lib/utils.dart +0 -42
- lib/utils/dialog.dart +1 -2
- lib/utils/side_menu_modal.dart +0 -53
- lib/widgets/bible_selector.dart +0 -57
- lib/widgets/book_selector.dart +0 -79
- lib/widgets/chapter_selector.dart +0 -52
- lib/widgets/header.dart +34 -18
- lib/widgets/play_button.dart +3 -4
- lib/widgets/scaffold_menu.dart +26 -0
- lib/widgets/sliver_heading.dart +40 -0
- lib/widgets/sliver_tile_grid.dart +33 -0
- lib/widgets/verse_list.dart +32 -0
- scripts/generate_audio.dart +10 -7
README.md
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# Only Bible App
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Fix dialog theme
|
|
4
|
-
targetSdkVersion 34
|
|
5
4
|
|
|
6
5
|
## Setup
|
|
7
6
|
|
|
@@ -44,6 +43,5 @@ firebase deploy
|
|
|
44
43
|
## Web
|
|
45
44
|
```agsl
|
|
46
45
|
https://only-bible-app.web.app
|
|
47
|
-
https://only-bible-app.firebaseapp.app
|
|
48
46
|
https://onlybible.app
|
|
49
47
|
```
|
android/app/build.gradle
CHANGED
|
@@ -51,8 +51,8 @@ android {
|
|
|
51
51
|
applicationId "sh.pyros.only_bible_app"
|
|
52
52
|
// You can update the following values to match your application needs.
|
|
53
53
|
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
|
54
|
-
minSdkVersion
|
|
54
|
+
minSdkVersion 30
|
|
55
|
-
targetSdkVersion
|
|
55
|
+
targetSdkVersion 34
|
|
56
56
|
versionCode flutterVersionCode.toInteger()
|
|
57
57
|
versionName flutterVersionName
|
|
58
58
|
}
|
android/build.gradle
CHANGED
|
@@ -8,7 +8,7 @@ buildscript {
|
|
|
8
8
|
dependencies {
|
|
9
9
|
classpath 'com.android.tools.build:gradle:7.3.0'
|
|
10
10
|
// START: FlutterFire Configuration
|
|
11
|
-
classpath 'com.google.gms:google-services:4.3.
|
|
11
|
+
classpath 'com.google.gms:google-services:4.3.14'
|
|
12
12
|
// END: FlutterFire Configuration
|
|
13
13
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
14
14
|
}
|
ios/Podfile.lock
CHANGED
|
@@ -13,9 +13,6 @@ PODS:
|
|
|
13
13
|
- FirebaseCoreInternal (10.13.0):
|
|
14
14
|
- "GoogleUtilities/NSData+zlib (~> 7.8)"
|
|
15
15
|
- Flutter (1.0.0)
|
|
16
|
-
- flutter_charset_detector_ios (0.0.1):
|
|
17
|
-
- Flutter
|
|
18
|
-
- UniversalDetector2 (= 2.0.1)
|
|
19
16
|
- flutter_native_splash (0.0.1):
|
|
20
17
|
- Flutter
|
|
21
18
|
- GoogleUtilities/Environment (7.11.5):
|
|
@@ -34,13 +31,11 @@ PODS:
|
|
|
34
31
|
- shared_preferences_foundation (0.0.1):
|
|
35
32
|
- Flutter
|
|
36
33
|
- FlutterMacOS
|
|
37
|
-
- UniversalDetector2 (2.0.1)
|
|
38
34
|
|
|
39
35
|
DEPENDENCIES:
|
|
40
36
|
- audio_session (from `.symlinks/plugins/audio_session/ios`)
|
|
41
37
|
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
|
42
38
|
- Flutter (from `Flutter`)
|
|
43
|
-
- flutter_charset_detector_ios (from `.symlinks/plugins/flutter_charset_detector_ios/ios`)
|
|
44
39
|
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
|
45
40
|
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
|
46
41
|
- just_audio (from `.symlinks/plugins/just_audio/ios`)
|
|
@@ -54,7 +49,6 @@ SPEC REPOS:
|
|
|
54
49
|
- FirebaseCoreInternal
|
|
55
50
|
- GoogleUtilities
|
|
56
51
|
- PromisesObjC
|
|
57
|
-
- UniversalDetector2
|
|
58
52
|
|
|
59
53
|
EXTERNAL SOURCES:
|
|
60
54
|
audio_session:
|
|
@@ -63,8 +57,6 @@ EXTERNAL SOURCES:
|
|
|
63
57
|
:path: ".symlinks/plugins/firebase_core/ios"
|
|
64
58
|
Flutter:
|
|
65
59
|
:path: Flutter
|
|
66
|
-
flutter_charset_detector_ios:
|
|
67
|
-
:path: ".symlinks/plugins/flutter_charset_detector_ios/ios"
|
|
68
60
|
flutter_native_splash:
|
|
69
61
|
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
|
70
62
|
integration_test:
|
|
@@ -83,7 +75,6 @@ SPEC CHECKSUMS:
|
|
|
83
75
|
FirebaseCore: f86a1394906b97ac445ae49c92552a9425831bed
|
|
84
76
|
FirebaseCoreInternal: b342e37cd4f5b4454ec34308f073420e7920858e
|
|
85
77
|
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
|
86
|
-
flutter_charset_detector_ios: 5157d0104855b9deb78e1395515a287197bc2d55
|
|
87
78
|
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
|
88
79
|
GoogleUtilities: 13e2c67ede716b8741c7989e26893d151b2b2084
|
|
89
80
|
integration_test: 13825b8a9334a850581300559b8839134b124670
|
|
@@ -91,7 +82,6 @@ SPEC CHECKSUMS:
|
|
|
91
82
|
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
|
92
83
|
PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4
|
|
93
84
|
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
|
|
94
|
-
UniversalDetector2: 7c9ffd935cf050eeb19edf7e90f6febe3743a1af
|
|
95
85
|
|
|
96
86
|
PODFILE CHECKSUM: 70d9d25280d0dd177a5f637cdb0f0b0b12c6a189
|
|
97
87
|
|
lib/app.dart
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import "package:flutter/material.dart";
|
|
2
2
|
import "package:flutter_gen/gen_l10n/app_localizations.dart";
|
|
3
3
|
import "package:flutter_reactive_value/flutter_reactive_value.dart";
|
|
4
|
-
import "package:go_router/go_router.dart";
|
|
5
|
-
import "package:only_bible_app/
|
|
4
|
+
import "package:only_bible_app/screens/chapter_view_screen.dart";
|
|
6
5
|
import "package:only_bible_app/state.dart";
|
|
7
|
-
import "package:only_bible_app/widgets/sidebar.dart";
|
|
8
6
|
import "package:only_bible_app/theme.dart";
|
|
9
7
|
|
|
10
8
|
class App extends StatelessWidget {
|
|
@@ -12,7 +10,7 @@ class App extends StatelessWidget {
|
|
|
12
10
|
|
|
13
11
|
@override
|
|
14
12
|
Widget build(BuildContext context) {
|
|
15
|
-
return MaterialApp
|
|
13
|
+
return MaterialApp(
|
|
16
14
|
title: "Only Bible App",
|
|
17
15
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
18
16
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
@@ -20,44 +18,45 @@ class App extends StatelessWidget {
|
|
|
20
18
|
themeMode: darkMode.reactiveValue(context) ? ThemeMode.dark : ThemeMode.light,
|
|
21
19
|
theme: lightTheme,
|
|
22
20
|
darkTheme: darkTheme,
|
|
21
|
+
home: ChapterViewScreen(book: bookIndex.value, chapter: chapterIndex.value),
|
|
23
|
-
routerConfig: GoRouter(
|
|
22
|
+
// routerConfig: GoRouter(
|
|
24
|
-
|
|
23
|
+
// debugLogDiagnostics: true,
|
|
25
|
-
|
|
24
|
+
// initialLocation: "/${selectedBible.value!.books[bookIndex.value].name}/${chapterIndex.value}",
|
|
26
|
-
|
|
25
|
+
// screens: [
|
|
27
|
-
|
|
26
|
+
// ShellRoute(
|
|
28
|
-
|
|
27
|
+
// builder: (context, state, child) {
|
|
29
|
-
|
|
28
|
+
// if (isWide(context)) {
|
|
29
|
+
// return Scaffold(
|
|
30
|
+
// backgroundColor: Theme.of(context).colorScheme.background,
|
|
31
|
+
// body: Row(
|
|
32
|
+
// children: [
|
|
33
|
+
// const Sidebar(),
|
|
34
|
+
// Flexible(
|
|
35
|
+
// child: child,
|
|
36
|
+
// ),
|
|
37
|
+
// ],
|
|
38
|
+
// ),
|
|
39
|
+
// );
|
|
40
|
+
// }
|
|
30
|
-
|
|
41
|
+
// return Scaffold(
|
|
31
|
-
|
|
42
|
+
// backgroundColor: Theme.of(context).colorScheme.background,
|
|
32
|
-
body: Row(
|
|
33
|
-
children: [
|
|
34
|
-
const Sidebar(),
|
|
35
|
-
Flexible(
|
|
36
|
-
child: child,
|
|
37
|
-
),
|
|
38
|
-
],
|
|
39
|
-
),
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
return Scaffold(
|
|
43
|
-
backgroundColor: Theme.of(context).colorScheme.background,
|
|
44
|
-
|
|
43
|
+
// body: SafeArea(
|
|
45
|
-
|
|
44
|
+
// child: child,
|
|
46
|
-
|
|
45
|
+
// ),
|
|
47
|
-
|
|
46
|
+
// );
|
|
48
|
-
|
|
47
|
+
// },
|
|
49
|
-
|
|
48
|
+
// screens: [
|
|
50
|
-
|
|
49
|
+
// GoRouteData.$route(
|
|
51
|
-
|
|
50
|
+
// path: "/:book/:chapter",
|
|
52
|
-
|
|
51
|
+
// factory: (GoRouterState state) => HomeScreen(
|
|
53
|
-
|
|
52
|
+
// book: state.pathParameters["book"]!,
|
|
54
|
-
|
|
53
|
+
// chapter: int.parse(state.pathParameters["chapter"]!),
|
|
54
|
+
// ),
|
|
55
|
+
// ),
|
|
56
|
+
// ],
|
|
57
|
+
// ),
|
|
58
|
+
// ],
|
|
55
|
-
|
|
59
|
+
// ),
|
|
56
|
-
),
|
|
57
|
-
],
|
|
58
|
-
),
|
|
59
|
-
],
|
|
60
|
-
),
|
|
61
60
|
);
|
|
62
61
|
}
|
|
63
62
|
}
|
lib/models.dart
CHANGED
|
@@ -148,3 +148,44 @@ final bibles = [
|
|
|
148
148
|
Bible(id: 10, name: "Telugu"),
|
|
149
149
|
Bible(id: 11, name: "Bengali"),
|
|
150
150
|
];
|
|
151
|
+
|
|
152
|
+
List<Book> getBibleFromText(String text) {
|
|
153
|
+
final List<Book> books = [];
|
|
154
|
+
final lines = text.split("\n");
|
|
155
|
+
for (var (index, line) in lines.indexed) {
|
|
156
|
+
// ignore last empty line
|
|
157
|
+
if (lines.length - 1 == index) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
var book = int.parse(line.substring(0, 2));
|
|
161
|
+
var chapter = int.parse(line.substring(3, 6));
|
|
162
|
+
// var verseNo = line.substring(7, 10);
|
|
163
|
+
var verseText = line.substring(11);
|
|
164
|
+
double start = 0;
|
|
165
|
+
double end = 0;
|
|
166
|
+
// if (item.length > 4) {
|
|
167
|
+
// start = double.parse(item[4]);
|
|
168
|
+
// end = double.parse(item[5]);
|
|
169
|
+
// }
|
|
170
|
+
if (books.length < book) {
|
|
171
|
+
books.add(
|
|
172
|
+
Book(
|
|
173
|
+
index: book - 1,
|
|
174
|
+
name: bookNames[book-1],
|
|
175
|
+
chapters: [],
|
|
176
|
+
),
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
if (books[book - 1].chapters.length < chapter) {
|
|
180
|
+
// ignore: prefer_const_constructors
|
|
181
|
+
books[book - 1].chapters.add(Chapter(verses: []));
|
|
182
|
+
}
|
|
183
|
+
books[book - 1].chapters[chapter - 1].verses.add(
|
|
184
|
+
Verse(
|
|
185
|
+
text: verseText,
|
|
186
|
+
audioRange: TimeRange(start: start, end: end),
|
|
187
|
+
),
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
return books;
|
|
191
|
+
}
|
lib/routes/home_screen.dart
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import "package:flutter/material.dart";
|
|
2
|
-
import 'package:flutter_reactive_value/flutter_reactive_value.dart';
|
|
3
|
-
import 'package:flutter_swipe_detector/flutter_swipe_detector.dart';
|
|
4
|
-
import 'package:go_router/go_router.dart';
|
|
5
|
-
import 'package:only_bible_app/widgets/header.dart';
|
|
6
|
-
import 'package:only_bible_app/widgets/verse_view.dart';
|
|
7
|
-
import 'package:only_bible_app/state.dart';
|
|
8
|
-
|
|
9
|
-
class HomeScreen extends GoRouteData {
|
|
10
|
-
final String book;
|
|
11
|
-
final int chapter;
|
|
12
|
-
|
|
13
|
-
HomeScreen({required this.book, required this.chapter}) {
|
|
14
|
-
selectedVerses.value.clear();
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
@override
|
|
18
|
-
Page buildPage(BuildContext context, GoRouterState state) {
|
|
19
|
-
if (slideTextDir.value == null) {
|
|
20
|
-
return NoTransitionPage(
|
|
21
|
-
child: SwipeDetector(
|
|
22
|
-
onSwipeLeft: (offset) {
|
|
23
|
-
onNext(context);
|
|
24
|
-
},
|
|
25
|
-
onSwipeRight: (offset) {
|
|
26
|
-
onPrevious(context);
|
|
27
|
-
},
|
|
28
|
-
child: const Column(
|
|
29
|
-
children: [
|
|
30
|
-
Header(),
|
|
31
|
-
Flexible(
|
|
32
|
-
child: VerseList(),
|
|
33
|
-
),
|
|
34
|
-
],
|
|
35
|
-
),
|
|
36
|
-
),
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
return CustomTransitionPage(
|
|
40
|
-
barrierDismissible: false,
|
|
41
|
-
barrierColor: Theme.of(context).colorScheme.background,
|
|
42
|
-
transitionDuration: const Duration(milliseconds: 360),
|
|
43
|
-
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
|
44
|
-
return SwipeDetector(
|
|
45
|
-
onSwipeLeft: (offset) {
|
|
46
|
-
onNext(context);
|
|
47
|
-
},
|
|
48
|
-
onSwipeRight: (offset) {
|
|
49
|
-
onPrevious(context);
|
|
50
|
-
},
|
|
51
|
-
child: Column(
|
|
52
|
-
children: [
|
|
53
|
-
const Header(),
|
|
54
|
-
Flexible(
|
|
55
|
-
child: SlideTransition(
|
|
56
|
-
textDirection: slideTextDir.value,
|
|
57
|
-
position: Tween(begin: const Offset(1, 0), end: Offset.zero)
|
|
58
|
-
.chain(CurveTween(curve: Curves.linear))
|
|
59
|
-
.animate(animation),
|
|
60
|
-
child: child,
|
|
61
|
-
),
|
|
62
|
-
),
|
|
63
|
-
],
|
|
64
|
-
),
|
|
65
|
-
);
|
|
66
|
-
},
|
|
67
|
-
child: const VerseList(),
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
class VerseList extends StatelessWidget {
|
|
73
|
-
const VerseList({super.key});
|
|
74
|
-
|
|
75
|
-
@override
|
|
76
|
-
Widget build(BuildContext context) {
|
|
77
|
-
final selectedBook = selectedBible.reactiveValue(context)!.books[bookIndex.value];
|
|
78
|
-
final verses = selectedBook.chapters[chapterIndex.value].verses;
|
|
79
|
-
return SelectionArea(
|
|
80
|
-
child: ListView.builder(
|
|
81
|
-
shrinkWrap: false,
|
|
82
|
-
padding: const EdgeInsets.only(
|
|
83
|
-
left: 20,
|
|
84
|
-
right: 20,
|
|
85
|
-
bottom: 20,
|
|
86
|
-
),
|
|
87
|
-
itemCount: verses.length,
|
|
88
|
-
itemBuilder: (BuildContext context, int index) {
|
|
89
|
-
final v = verses[index];
|
|
90
|
-
return Container(
|
|
91
|
-
margin: const EdgeInsets.symmetric(vertical: 6),
|
|
92
|
-
child: VerseText(index: index, text: v.text),
|
|
93
|
-
);
|
|
94
|
-
},
|
|
95
|
-
),
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
}
|
lib/screens/bible_select_screen.dart
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import "package:flutter/material.dart";
|
|
2
|
+
import "package:only_bible_app/state.dart";
|
|
3
|
+
import "package:only_bible_app/models.dart";
|
|
4
|
+
import "package:only_bible_app/widgets/scaffold_menu.dart";
|
|
5
|
+
import "package:only_bible_app/widgets/sliver_heading.dart";
|
|
6
|
+
import "package:only_bible_app/widgets/sliver_tile_grid.dart";
|
|
7
|
+
|
|
8
|
+
class BibleSelectScreen extends StatelessWidget {
|
|
9
|
+
const BibleSelectScreen({super.key});
|
|
10
|
+
|
|
11
|
+
@override
|
|
12
|
+
Widget build(BuildContext context) {
|
|
13
|
+
return ScaffoldMenu(
|
|
14
|
+
child: CustomScrollView(
|
|
15
|
+
slivers: [
|
|
16
|
+
const SliverHeading(title: "Bibles", showClose: true),
|
|
17
|
+
SliverTileGrid(
|
|
18
|
+
listType: ListType.large,
|
|
19
|
+
children: List.of(
|
|
20
|
+
bibles.map((bible) {
|
|
21
|
+
return TextButton(
|
|
22
|
+
child: Text(bible.name),
|
|
23
|
+
onPressed: () => changeBible(context, bible.id),
|
|
24
|
+
);
|
|
25
|
+
}),
|
|
26
|
+
),
|
|
27
|
+
),
|
|
28
|
+
],
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
lib/screens/book_select_screen.dart
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import "package:flutter/material.dart";
|
|
2
|
+
import "package:only_bible_app/state.dart";
|
|
3
|
+
import "package:only_bible_app/widgets/scaffold_menu.dart";
|
|
4
|
+
import "package:only_bible_app/screens/chapter_select_screen.dart";
|
|
5
|
+
import "package:only_bible_app/widgets/sliver_heading.dart";
|
|
6
|
+
import "package:only_bible_app/widgets/sliver_tile_grid.dart";
|
|
7
|
+
|
|
8
|
+
class BookSelectScreen extends StatelessWidget {
|
|
9
|
+
const BookSelectScreen({super.key});
|
|
10
|
+
|
|
11
|
+
onBookSelected(BuildContext context, int index) {
|
|
12
|
+
Navigator.of(context).pushReplacement(
|
|
13
|
+
PageRouteBuilder(
|
|
14
|
+
opaque: false,
|
|
15
|
+
transitionDuration: Duration.zero,
|
|
16
|
+
reverseTransitionDuration: Duration.zero,
|
|
17
|
+
pageBuilder: (context, _, __) => ChapterSelectScreen(
|
|
18
|
+
selectedBookIndex: index,
|
|
19
|
+
),
|
|
20
|
+
),
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@override
|
|
25
|
+
Widget build(BuildContext context) {
|
|
26
|
+
return ScaffoldMenu(
|
|
27
|
+
child: CustomScrollView(
|
|
28
|
+
slivers: [
|
|
29
|
+
const SliverHeading(title: "Old Testament", showClose: true),
|
|
30
|
+
SliverTileGrid(
|
|
31
|
+
children: List.of(
|
|
32
|
+
selectedBible.value!.getOldBooks().map((book) {
|
|
33
|
+
return TextButton(
|
|
34
|
+
child: Text(book.shortName()),
|
|
35
|
+
onPressed: () => onBookSelected(context, book.index),
|
|
36
|
+
);
|
|
37
|
+
}),
|
|
38
|
+
),
|
|
39
|
+
),
|
|
40
|
+
const SliverHeading(title: "New Testament", top: 30, bottom: 20),
|
|
41
|
+
SliverTileGrid(
|
|
42
|
+
children: List.of(
|
|
43
|
+
selectedBible.value!.getNewBooks().map((book) {
|
|
44
|
+
return TextButton(
|
|
45
|
+
child: Text(book.shortName()),
|
|
46
|
+
onPressed: () => onBookSelected(context, book.index),
|
|
47
|
+
);
|
|
48
|
+
}),
|
|
49
|
+
),
|
|
50
|
+
),
|
|
51
|
+
],
|
|
52
|
+
),
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
lib/screens/chapter_select_screen.dart
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import "package:flutter/material.dart";
|
|
2
|
+
import "package:only_bible_app/state.dart";
|
|
3
|
+
import "package:only_bible_app/widgets/scaffold_menu.dart";
|
|
4
|
+
import "package:only_bible_app/widgets/sliver_tile_grid.dart";
|
|
5
|
+
import "package:only_bible_app/widgets/sliver_heading.dart";
|
|
6
|
+
|
|
7
|
+
class ChapterSelectScreen extends StatelessWidget {
|
|
8
|
+
final int selectedBookIndex;
|
|
9
|
+
|
|
10
|
+
const ChapterSelectScreen({super.key, required this.selectedBookIndex});
|
|
11
|
+
|
|
12
|
+
onChapterSelected(BuildContext context, int index) {
|
|
13
|
+
navigateBookChapter(context, selectedBookIndex, index, true);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@override
|
|
17
|
+
Widget build(BuildContext context) {
|
|
18
|
+
final book = selectedBible.value!.books[selectedBookIndex];
|
|
19
|
+
return ScaffoldMenu(
|
|
20
|
+
child: CustomScrollView(
|
|
21
|
+
slivers: [
|
|
22
|
+
SliverHeading(title: book.name, showClose: true),
|
|
23
|
+
SliverTileGrid(
|
|
24
|
+
children: List.generate(book.chapters.length, (index) {
|
|
25
|
+
return TextButton(
|
|
26
|
+
child: Text("${index + 1}"),
|
|
27
|
+
onPressed: () => onChapterSelected(context, index),
|
|
28
|
+
);
|
|
29
|
+
}),
|
|
30
|
+
),
|
|
31
|
+
],
|
|
32
|
+
),
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|
lib/screens/chapter_view_screen.dart
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import "package:flutter/material.dart";
|
|
2
|
+
import "package:flutter_reactive_value/flutter_reactive_value.dart";
|
|
3
|
+
import "package:flutter_swipe_detector/flutter_swipe_detector.dart";
|
|
4
|
+
import "package:only_bible_app/widgets/header.dart";
|
|
5
|
+
import "package:only_bible_app/state.dart";
|
|
6
|
+
import "package:only_bible_app/widgets/play_button.dart";
|
|
7
|
+
import "package:only_bible_app/widgets/sidebar.dart";
|
|
8
|
+
import "package:only_bible_app/widgets/verse_list.dart";
|
|
9
|
+
|
|
10
|
+
class ChapterViewScreen extends StatelessWidget {
|
|
11
|
+
final int book;
|
|
12
|
+
final int chapter;
|
|
13
|
+
|
|
14
|
+
const ChapterViewScreen({super.key, required this.book, required this.chapter});
|
|
15
|
+
|
|
16
|
+
@override
|
|
17
|
+
Widget build(BuildContext context) {
|
|
18
|
+
final isDesktop = isWide(context);
|
|
19
|
+
final showPlay = selectedVerses.reactiveValue(context).isNotEmpty;
|
|
20
|
+
return Scaffold(
|
|
21
|
+
backgroundColor: Theme.of(context).colorScheme.background,
|
|
22
|
+
bottomSheet: !isDesktop && showPlay
|
|
23
|
+
? BottomSheet(
|
|
24
|
+
enableDrag: false,
|
|
25
|
+
onClosing: () {},
|
|
26
|
+
builder: (BuildContext ctx) => Container(
|
|
27
|
+
padding: const EdgeInsets.only(bottom: 40),
|
|
28
|
+
child: const Row(
|
|
29
|
+
mainAxisAlignment: MainAxisAlignment.center,
|
|
30
|
+
children: [
|
|
31
|
+
PlayButton(),
|
|
32
|
+
],
|
|
33
|
+
),
|
|
34
|
+
),
|
|
35
|
+
)
|
|
36
|
+
: null,
|
|
37
|
+
body: SafeArea(
|
|
38
|
+
child: SwipeDetector(
|
|
39
|
+
onSwipeLeft: (offset) {
|
|
40
|
+
onNext(context);
|
|
41
|
+
},
|
|
42
|
+
onSwipeRight: (offset) {
|
|
43
|
+
onPrevious(context);
|
|
44
|
+
},
|
|
45
|
+
child: Row(
|
|
46
|
+
children: [
|
|
47
|
+
if (isWide(context)) const Sidebar(),
|
|
48
|
+
const Flexible(
|
|
49
|
+
child: Column(
|
|
50
|
+
children: [
|
|
51
|
+
Header(),
|
|
52
|
+
Flexible(
|
|
53
|
+
child: VerseList(),
|
|
54
|
+
),
|
|
55
|
+
],
|
|
56
|
+
),
|
|
57
|
+
),
|
|
58
|
+
],
|
|
59
|
+
),
|
|
60
|
+
),
|
|
61
|
+
),
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
lib/state.dart
CHANGED
|
@@ -1,40 +1,39 @@
|
|
|
1
|
-
import
|
|
1
|
+
import "dart:convert";
|
|
2
|
-
import
|
|
2
|
+
import "package:flutter/foundation.dart" show defaultTargetPlatform, TargetPlatform;
|
|
3
|
-
import
|
|
3
|
+
import "package:flutter/services.dart";
|
|
4
|
-
import
|
|
4
|
+
import "package:flutter/material.dart";
|
|
5
|
-
import
|
|
5
|
+
import "package:flutter_persistent_value_notifier/flutter_persistent_value_notifier.dart";
|
|
6
|
-
import
|
|
6
|
+
import "package:flutter_reactive_value/flutter_reactive_value.dart";
|
|
7
|
-
import 'package:go_router/go_router.dart';
|
|
8
|
-
import
|
|
7
|
+
import "package:just_audio/just_audio.dart";
|
|
9
|
-
import
|
|
8
|
+
import "package:only_bible_app/screens/chapter_view_screen.dart";
|
|
10
|
-
import
|
|
9
|
+
import "package:only_bible_app/utils/dialog.dart";
|
|
11
|
-
import
|
|
10
|
+
import "package:only_bible_app/models.dart";
|
|
12
11
|
|
|
13
12
|
final shellNavigatorKey = GlobalKey<NavigatorState>();
|
|
14
13
|
final routeNavigatorKey = GlobalKey<NavigatorState>();
|
|
15
14
|
|
|
16
15
|
final darkMode = PersistentValueNotifier<bool>(
|
|
17
|
-
sharedPreferencesKey:
|
|
16
|
+
sharedPreferencesKey: "darkMode",
|
|
18
17
|
initialValue: false,
|
|
19
18
|
);
|
|
20
19
|
|
|
21
20
|
final fontBold = PersistentValueNotifier<bool>(
|
|
22
|
-
sharedPreferencesKey:
|
|
21
|
+
sharedPreferencesKey: "fontBold",
|
|
23
22
|
initialValue: false,
|
|
24
23
|
);
|
|
25
24
|
|
|
26
25
|
final selectedBibleId = PersistentValueNotifier(
|
|
27
|
-
sharedPreferencesKey:
|
|
26
|
+
sharedPreferencesKey: "selectedBibleId",
|
|
28
27
|
initialValue: 1,
|
|
29
28
|
);
|
|
30
29
|
|
|
31
30
|
final bookIndex = PersistentValueNotifier<int>(
|
|
32
|
-
sharedPreferencesKey:
|
|
31
|
+
sharedPreferencesKey: "bookIndex",
|
|
33
32
|
initialValue: 0,
|
|
34
33
|
);
|
|
35
34
|
|
|
36
35
|
final chapterIndex = PersistentValueNotifier<int>(
|
|
37
|
-
sharedPreferencesKey:
|
|
36
|
+
sharedPreferencesKey: "chapterIndex",
|
|
38
37
|
initialValue: 0,
|
|
39
38
|
);
|
|
40
39
|
|
|
@@ -94,19 +93,44 @@ changeBible(BuildContext context, int i) {
|
|
|
94
93
|
Navigator.of(context).pop();
|
|
95
94
|
}
|
|
96
95
|
|
|
97
|
-
|
|
96
|
+
createSlideRoute({required BuildContext context, TextDirection? slideDir, required Widget page}) {
|
|
98
|
-
if (isWide(context) ||
|
|
97
|
+
if (isWide(context) || slideDir == null) {
|
|
98
|
+
return PageRouteBuilder(
|
|
99
|
-
|
|
99
|
+
pageBuilder: (context, _, __) {
|
|
100
|
-
|
|
100
|
+
return page;
|
|
101
|
+
},
|
|
101
|
-
|
|
102
|
+
);
|
|
102
103
|
}
|
|
104
|
+
return PageRouteBuilder(
|
|
105
|
+
pageBuilder: (context, animation, secondaryAnimation) => page,
|
|
106
|
+
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
|
107
|
+
const begin = Offset(1.0, 0.0);
|
|
108
|
+
const end = Offset.zero;
|
|
109
|
+
const curve = Curves.ease;
|
|
110
|
+
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
|
|
111
|
+
return SlideTransition(
|
|
112
|
+
textDirection: slideDir,
|
|
113
|
+
position: animation.drive(tween),
|
|
114
|
+
child: child,
|
|
115
|
+
);
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
navigateBookChapter(BuildContext context, int book, int chapter, bool noAnim) {
|
|
121
|
+
final slideDir = bookIndex.value > book || chapterIndex.value > chapter ? TextDirection.rtl : TextDirection.ltr;
|
|
122
|
+
// TODO: add bible param here maybe
|
|
123
|
+
// route: /bible/book/chapter
|
|
103
124
|
bookIndex.value = book;
|
|
104
125
|
chapterIndex.value = chapter;
|
|
105
|
-
context.push("/${selectedBible.value!.books[book].name}/$chapter");
|
|
106
|
-
// Use this or use navigatorKey once header moves scaffold
|
|
107
|
-
|
|
126
|
+
selectedVerses.value.clear();
|
|
108
|
-
|
|
127
|
+
Navigator.of(context).push(
|
|
128
|
+
createSlideRoute(
|
|
109
|
-
|
|
129
|
+
context: context,
|
|
130
|
+
slideDir: noAnim ? null : slideDir,
|
|
131
|
+
page: ChapterViewScreen(book: book, chapter: chapter),
|
|
132
|
+
),
|
|
133
|
+
);
|
|
110
134
|
}
|
|
111
135
|
|
|
112
136
|
onNext(BuildContext context) {
|
|
@@ -161,9 +185,9 @@ onPlay(BuildContext context) async {
|
|
|
161
185
|
isPlaying.value = true;
|
|
162
186
|
for (final v in selectedVerses.value) {
|
|
163
187
|
final bibleName = selectedBible.value!.name;
|
|
164
|
-
final book = (bookIndex.value + 1).toString().padLeft(2,
|
|
188
|
+
final book = (bookIndex.value + 1).toString().padLeft(2, "0");
|
|
165
|
-
final chapter = (chapterIndex.value + 1).toString().padLeft(3,
|
|
189
|
+
final chapter = (chapterIndex.value + 1).toString().padLeft(3, "0");
|
|
166
|
-
final verse = (v + 1).toString().padLeft(3,
|
|
190
|
+
final verse = (v + 1).toString().padLeft(3, "0");
|
|
167
191
|
await player.setUrl(
|
|
168
192
|
"http://localhost:3000/$bibleName/$book-$chapter-$verse.mp3",
|
|
169
193
|
);
|
lib/theme.dart
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import "package:flutter/material.dart";
|
|
2
2
|
|
|
3
3
|
final lightTheme = ThemeData(
|
|
4
4
|
brightness: Brightness.light,
|
|
@@ -12,6 +12,31 @@ final lightTheme = ThemeData(
|
|
|
12
12
|
hoverColor: const Color(0xAAF8D0DC),
|
|
13
13
|
dividerColor: Colors.black,
|
|
14
14
|
shadowColor: Colors.black,
|
|
15
|
+
bottomSheetTheme: const BottomSheetThemeData(
|
|
16
|
+
elevation: 10,
|
|
17
|
+
shadowColor: Colors.black,
|
|
18
|
+
backgroundColor: Colors.white,
|
|
19
|
+
surfaceTintColor: Colors.white,
|
|
20
|
+
shape: Border(
|
|
21
|
+
top: BorderSide(
|
|
22
|
+
width: 1.5,
|
|
23
|
+
color: Colors.black,
|
|
24
|
+
),
|
|
25
|
+
),
|
|
26
|
+
),
|
|
27
|
+
dialogTheme: const DialogTheme(
|
|
28
|
+
elevation: 10,
|
|
29
|
+
// TODO: get this to inherit from top like darkTheme does
|
|
30
|
+
shadowColor: Colors.black,
|
|
31
|
+
backgroundColor: Colors.white,
|
|
32
|
+
surfaceTintColor: Colors.white,
|
|
33
|
+
shape: Border(
|
|
34
|
+
top: BorderSide(
|
|
35
|
+
width: 1.5,
|
|
36
|
+
color: Colors.black,
|
|
37
|
+
),
|
|
38
|
+
),
|
|
39
|
+
),
|
|
15
40
|
popupMenuTheme: const PopupMenuThemeData(
|
|
16
41
|
enableFeedback: true,
|
|
17
42
|
elevation: 4,
|
|
@@ -86,6 +111,24 @@ final darkTheme = ThemeData(
|
|
|
86
111
|
hoverColor: const Color(0xAA5D4979),
|
|
87
112
|
dividerColor: Colors.white,
|
|
88
113
|
shadowColor: Colors.white,
|
|
114
|
+
bottomSheetTheme: const BottomSheetThemeData(
|
|
115
|
+
elevation: 1,
|
|
116
|
+
shape: Border(
|
|
117
|
+
top: BorderSide(
|
|
118
|
+
width: 1.5,
|
|
119
|
+
color: Color(0xAA5D4979),
|
|
120
|
+
),
|
|
121
|
+
),
|
|
122
|
+
),
|
|
123
|
+
dialogTheme: const DialogTheme(
|
|
124
|
+
elevation: 1,
|
|
125
|
+
shape: Border(
|
|
126
|
+
top: BorderSide(
|
|
127
|
+
width: 1.5,
|
|
128
|
+
color: Color(0xAA5D4979),
|
|
129
|
+
),
|
|
130
|
+
),
|
|
131
|
+
),
|
|
89
132
|
popupMenuTheme: lightTheme.popupMenuTheme,
|
|
90
133
|
colorScheme: const ColorScheme.dark(
|
|
91
134
|
background: Color(0xFF1F1F22),
|
lib/utils.dart
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import 'package:only_bible_app/models.dart';
|
|
2
|
-
|
|
3
|
-
List<Book> getBibleFromText(String text) {
|
|
4
|
-
final List<Book> books = [];
|
|
5
|
-
final lines = text.split("\n");
|
|
6
|
-
for (var (index, line) in lines.indexed) {
|
|
7
|
-
// ignore last empty line
|
|
8
|
-
if (lines.length - 1 == index) {
|
|
9
|
-
continue;
|
|
10
|
-
}
|
|
11
|
-
var book = int.parse(line.substring(0, 2));
|
|
12
|
-
var chapter = int.parse(line.substring(3, 6));
|
|
13
|
-
// var verseNo = line.substring(7, 10);
|
|
14
|
-
var verseText = line.substring(11);
|
|
15
|
-
double start = 0;
|
|
16
|
-
double end = 0;
|
|
17
|
-
// if (item.length > 4) {
|
|
18
|
-
// start = double.parse(item[4]);
|
|
19
|
-
// end = double.parse(item[5]);
|
|
20
|
-
// }
|
|
21
|
-
if (books.length < book) {
|
|
22
|
-
books.add(
|
|
23
|
-
Book(
|
|
24
|
-
index: book - 1,
|
|
25
|
-
name: bookNames[book-1],
|
|
26
|
-
chapters: [],
|
|
27
|
-
),
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
if (books[book - 1].chapters.length < chapter) {
|
|
31
|
-
// ignore: prefer_const_constructors
|
|
32
|
-
books[book - 1].chapters.add(Chapter(verses: []));
|
|
33
|
-
}
|
|
34
|
-
books[book - 1].chapters[chapter - 1].verses.add(
|
|
35
|
-
Verse(
|
|
36
|
-
text: verseText,
|
|
37
|
-
audioRange: TimeRange(start: start, end: end),
|
|
38
|
-
),
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
return books;
|
|
42
|
-
}
|
lib/utils/dialog.dart
CHANGED
|
@@ -41,8 +41,7 @@ showAlert(BuildContext context, String title, String message) {
|
|
|
41
41
|
actions: [
|
|
42
42
|
TextButton(
|
|
43
43
|
onPressed: () {
|
|
44
|
-
|
|
44
|
+
Navigator.of(context).pop();
|
|
45
|
-
context.pop();
|
|
46
45
|
},
|
|
47
46
|
child: const Text("OK"),
|
|
48
47
|
),
|
lib/utils/side_menu_modal.dart
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import "package:flutter/material.dart";
|
|
2
|
-
import "package:only_bible_app/state.dart";
|
|
3
|
-
|
|
4
|
-
class SideMenuModal extends ModalRoute<void> {
|
|
5
|
-
final Widget child;
|
|
6
|
-
|
|
7
|
-
SideMenuModal({required this.child});
|
|
8
|
-
|
|
9
|
-
@override
|
|
10
|
-
Duration get transitionDuration => Duration.zero;
|
|
11
|
-
|
|
12
|
-
@override
|
|
13
|
-
Duration get reverseTransitionDuration => Duration.zero;
|
|
14
|
-
|
|
15
|
-
@override
|
|
16
|
-
bool get opaque => false;
|
|
17
|
-
|
|
18
|
-
@override
|
|
19
|
-
bool get barrierDismissible => true;
|
|
20
|
-
|
|
21
|
-
@override
|
|
22
|
-
Color get barrierColor => Colors.black.withOpacity(0.7);
|
|
23
|
-
|
|
24
|
-
@override
|
|
25
|
-
String? get barrierLabel => "Route";
|
|
26
|
-
|
|
27
|
-
@override
|
|
28
|
-
bool get maintainState => false;
|
|
29
|
-
|
|
30
|
-
@override
|
|
31
|
-
Widget buildPage(
|
|
32
|
-
BuildContext context,
|
|
33
|
-
Animation<double> animation,
|
|
34
|
-
Animation<double> secondaryAnimation,
|
|
35
|
-
) {
|
|
36
|
-
return Material(
|
|
37
|
-
type: MaterialType.transparency,
|
|
38
|
-
child: Container(
|
|
39
|
-
color: Theme.of(context).colorScheme.background,
|
|
40
|
-
margin: EdgeInsets.only(left: 0, right: isWide(context) ? 650 : 0),
|
|
41
|
-
child: Container(
|
|
42
|
-
margin: EdgeInsets.only(top: isWide(context) ? 5 : 0, left: 20, right: 20),
|
|
43
|
-
child: isWide(context)
|
|
44
|
-
? child
|
|
45
|
-
: SlideTransition(
|
|
46
|
-
position: Tween(begin: const Offset(-1, 0), end: Offset.zero).animate(animation),
|
|
47
|
-
child: child,
|
|
48
|
-
),
|
|
49
|
-
),
|
|
50
|
-
),
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
}
|
lib/widgets/bible_selector.dart
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import "package:flutter/material.dart";
|
|
2
|
-
import "package:only_bible_app/state.dart";
|
|
3
|
-
import "package:only_bible_app/models.dart";
|
|
4
|
-
|
|
5
|
-
class BibleSelector extends StatelessWidget {
|
|
6
|
-
const BibleSelector({super.key});
|
|
7
|
-
|
|
8
|
-
@override
|
|
9
|
-
Widget build(BuildContext context) {
|
|
10
|
-
return Column(
|
|
11
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
12
|
-
children: [
|
|
13
|
-
Container(
|
|
14
|
-
margin: const EdgeInsets.only(bottom: 10),
|
|
15
|
-
child: Row(
|
|
16
|
-
mainAxisAlignment: MainAxisAlignment.center,
|
|
17
|
-
children: [
|
|
18
|
-
Expanded(
|
|
19
|
-
child: Text("Bibles", style: Theme.of(context).textTheme.headlineMedium),
|
|
20
|
-
),
|
|
21
|
-
Container(
|
|
22
|
-
margin: const EdgeInsets.only(right: 10),
|
|
23
|
-
child: IconButton(
|
|
24
|
-
icon: const Icon(Icons.close, size: 28),
|
|
25
|
-
onPressed: () {
|
|
26
|
-
Navigator.of(context).pop();
|
|
27
|
-
},
|
|
28
|
-
),
|
|
29
|
-
)
|
|
30
|
-
],
|
|
31
|
-
),
|
|
32
|
-
),
|
|
33
|
-
Expanded(
|
|
34
|
-
child: GridView.count(
|
|
35
|
-
crossAxisCount: 2,
|
|
36
|
-
padding: EdgeInsets.zero,
|
|
37
|
-
shrinkWrap: true,
|
|
38
|
-
crossAxisSpacing: 0,
|
|
39
|
-
mainAxisSpacing: 0,
|
|
40
|
-
childAspectRatio: 4,
|
|
41
|
-
children: List.of(
|
|
42
|
-
bibles.map((bible) {
|
|
43
|
-
return Container(
|
|
44
|
-
margin: const EdgeInsets.only(right: 16, bottom: 16),
|
|
45
|
-
child: TextButton(
|
|
46
|
-
child: Text(bible.name),
|
|
47
|
-
onPressed: () => changeBible(context, bible.id),
|
|
48
|
-
),
|
|
49
|
-
);
|
|
50
|
-
}),
|
|
51
|
-
),
|
|
52
|
-
),
|
|
53
|
-
),
|
|
54
|
-
],
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
}
|
lib/widgets/book_selector.dart
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import "package:flutter/material.dart";
|
|
2
|
-
import "package:only_bible_app/state.dart";
|
|
3
|
-
import "package:only_bible_app/widgets/chapter_selector.dart";
|
|
4
|
-
import "package:only_bible_app/utils/side_menu_modal.dart";
|
|
5
|
-
|
|
6
|
-
class BookSelector extends StatelessWidget {
|
|
7
|
-
const BookSelector({super.key});
|
|
8
|
-
|
|
9
|
-
onBookSelected(BuildContext context, int index) {
|
|
10
|
-
Navigator.of(context).pushReplacement(
|
|
11
|
-
SideMenuModal(
|
|
12
|
-
child: ChapterSelector(
|
|
13
|
-
selectedBookIndex: index,
|
|
14
|
-
),
|
|
15
|
-
),
|
|
16
|
-
);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
@override
|
|
20
|
-
Widget build(BuildContext context) {
|
|
21
|
-
return CustomScrollView(
|
|
22
|
-
slivers: [
|
|
23
|
-
SliverToBoxAdapter(
|
|
24
|
-
child: Container(
|
|
25
|
-
margin: const EdgeInsets.only(bottom: 10),
|
|
26
|
-
child: Row(
|
|
27
|
-
mainAxisAlignment: MainAxisAlignment.center,
|
|
28
|
-
children: [
|
|
29
|
-
Expanded(
|
|
30
|
-
child: Text("Old Testament", style: Theme.of(context).textTheme.headlineMedium),
|
|
31
|
-
),
|
|
32
|
-
IconButton(
|
|
33
|
-
icon: const Icon(Icons.close, size: 28),
|
|
34
|
-
onPressed: () {
|
|
35
|
-
Navigator.of(context).pop();
|
|
36
|
-
},
|
|
37
|
-
),
|
|
38
|
-
],
|
|
39
|
-
),
|
|
40
|
-
),
|
|
41
|
-
),
|
|
42
|
-
SliverGrid.count(
|
|
43
|
-
crossAxisCount: 6,
|
|
44
|
-
crossAxisSpacing: 16,
|
|
45
|
-
mainAxisSpacing: 16,
|
|
46
|
-
childAspectRatio: 1.6,
|
|
47
|
-
children: List.of(
|
|
48
|
-
selectedBible.value!.getOldBooks().map((book) {
|
|
49
|
-
return TextButton(
|
|
50
|
-
child: Text(book.shortName()),
|
|
51
|
-
onPressed: () => onBookSelected(context, book.index),
|
|
52
|
-
);
|
|
53
|
-
}),
|
|
54
|
-
),
|
|
55
|
-
),
|
|
56
|
-
SliverToBoxAdapter(
|
|
57
|
-
child: Container(
|
|
58
|
-
margin: const EdgeInsets.only(top: 30, bottom: 20),
|
|
59
|
-
child: Text("New Testament", style: Theme.of(context).textTheme.headlineMedium),
|
|
60
|
-
),
|
|
61
|
-
),
|
|
62
|
-
SliverGrid.count(
|
|
63
|
-
crossAxisCount: 6,
|
|
64
|
-
crossAxisSpacing: 16,
|
|
65
|
-
mainAxisSpacing: 16,
|
|
66
|
-
childAspectRatio: 1.6,
|
|
67
|
-
children: List.of(
|
|
68
|
-
selectedBible.value!.getNewBooks().map((book) {
|
|
69
|
-
return TextButton(
|
|
70
|
-
child: Text(book.shortName()),
|
|
71
|
-
onPressed: () => onBookSelected(context, book.index),
|
|
72
|
-
);
|
|
73
|
-
}),
|
|
74
|
-
),
|
|
75
|
-
),
|
|
76
|
-
],
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
}
|
lib/widgets/chapter_selector.dart
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import "package:flutter/material.dart";
|
|
2
|
-
import "package:only_bible_app/state.dart";
|
|
3
|
-
|
|
4
|
-
class ChapterSelector extends StatelessWidget {
|
|
5
|
-
final int selectedBookIndex;
|
|
6
|
-
|
|
7
|
-
const ChapterSelector({super.key, required this.selectedBookIndex});
|
|
8
|
-
|
|
9
|
-
onChapterSelected(BuildContext context, int index) {
|
|
10
|
-
navigateBookChapter(context, selectedBookIndex, index, true);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
@override
|
|
14
|
-
Widget build(BuildContext context) {
|
|
15
|
-
final book = selectedBible.value!.books[selectedBookIndex];
|
|
16
|
-
return CustomScrollView(
|
|
17
|
-
slivers: [
|
|
18
|
-
SliverToBoxAdapter(
|
|
19
|
-
child: Container(
|
|
20
|
-
margin: const EdgeInsets.only(bottom: 10),
|
|
21
|
-
child: Row(
|
|
22
|
-
mainAxisAlignment: MainAxisAlignment.center,
|
|
23
|
-
children: [
|
|
24
|
-
Expanded(
|
|
25
|
-
child: Text(book.name, style: Theme.of(context).textTheme.headlineMedium),
|
|
26
|
-
),
|
|
27
|
-
IconButton(
|
|
28
|
-
icon: const Icon(Icons.close, size: 28),
|
|
29
|
-
onPressed: () {
|
|
30
|
-
Navigator.of(context).pop();
|
|
31
|
-
},
|
|
32
|
-
),
|
|
33
|
-
],
|
|
34
|
-
),
|
|
35
|
-
),
|
|
36
|
-
),
|
|
37
|
-
SliverGrid.count(
|
|
38
|
-
crossAxisCount: 6,
|
|
39
|
-
crossAxisSpacing: 16,
|
|
40
|
-
mainAxisSpacing: 16,
|
|
41
|
-
childAspectRatio: 1.6,
|
|
42
|
-
children: List.generate(book.chapters.length, (index) {
|
|
43
|
-
return TextButton(
|
|
44
|
-
child: Text("${index + 1}"),
|
|
45
|
-
onPressed: () => onChapterSelected(context, index),
|
|
46
|
-
);
|
|
47
|
-
}),
|
|
48
|
-
),
|
|
49
|
-
],
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
}
|
lib/widgets/header.dart
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import "package:flutter/material.dart";
|
|
2
2
|
import "package:flutter_reactive_value/flutter_reactive_value.dart";
|
|
3
|
+
import "package:only_bible_app/screens/bible_select_screen.dart";
|
|
3
|
-
import "package:only_bible_app/
|
|
4
|
+
import "package:only_bible_app/screens/book_select_screen.dart";
|
|
4
5
|
import "package:only_bible_app/widgets/play_button.dart";
|
|
5
|
-
import "package:only_bible_app/utils/side_menu_modal.dart";
|
|
6
6
|
import "package:only_bible_app/widgets/menu.dart";
|
|
7
7
|
import "package:only_bible_app/state.dart";
|
|
8
|
-
import "package:only_bible_app/widgets/bible_selector.dart";
|
|
9
8
|
|
|
10
9
|
class Header extends StatelessWidget {
|
|
11
10
|
const Header({super.key});
|
|
@@ -15,6 +14,7 @@ class Header extends StatelessWidget {
|
|
|
15
14
|
final book = bookIndex.reactiveValue(context);
|
|
16
15
|
final chapter = chapterIndex.reactiveValue(context);
|
|
17
16
|
final selectedBook = selectedBible.value!.books[book];
|
|
17
|
+
final isDesktop = isWide(context);
|
|
18
18
|
return Container(
|
|
19
19
|
padding: EdgeInsets.only(
|
|
20
20
|
left: 20,
|
|
@@ -43,28 +43,44 @@ class Header extends StatelessWidget {
|
|
|
43
43
|
style: Theme.of(context).textTheme.headlineMedium,
|
|
44
44
|
),
|
|
45
45
|
onPressed: () {
|
|
46
|
+
Navigator.of(context).push(
|
|
47
|
+
PageRouteBuilder(
|
|
48
|
+
opaque: false,
|
|
49
|
+
transitionDuration: Duration.zero,
|
|
50
|
+
reverseTransitionDuration: Duration.zero,
|
|
46
|
-
|
|
51
|
+
pageBuilder: (context, _, __) => const BookSelectScreen(),
|
|
52
|
+
),
|
|
53
|
+
);
|
|
47
54
|
},
|
|
48
55
|
),
|
|
49
56
|
Row(
|
|
50
57
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
51
58
|
children: [
|
|
59
|
+
if (isDesktop)
|
|
52
|
-
|
|
60
|
+
Container(
|
|
53
|
-
|
|
61
|
+
margin: EdgeInsets.only(right: isWide(context) ? 10 : 8),
|
|
54
|
-
|
|
62
|
+
child: TextButton(
|
|
55
|
-
|
|
63
|
+
style: TextButton.styleFrom(
|
|
56
|
-
|
|
64
|
+
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
65
|
+
),
|
|
66
|
+
child: Text(selectedBible.reactiveValue(context)!.name),
|
|
67
|
+
onPressed: () {
|
|
68
|
+
Navigator.of(context).push(
|
|
69
|
+
PageRouteBuilder(
|
|
70
|
+
opaque: false,
|
|
71
|
+
transitionDuration: Duration.zero,
|
|
72
|
+
reverseTransitionDuration: Duration.zero,
|
|
73
|
+
pageBuilder: (context, _, __) => const BibleSelectScreen(),
|
|
74
|
+
),
|
|
75
|
+
);
|
|
76
|
+
},
|
|
57
77
|
),
|
|
58
|
-
child: Text(selectedBible.reactiveValue(context)!.name),
|
|
59
|
-
onPressed: () {
|
|
60
|
-
Navigator.of(context).push(SideMenuModal(child: const BibleSelector()));
|
|
61
|
-
},
|
|
62
78
|
),
|
|
79
|
+
if (isDesktop)
|
|
80
|
+
Container(
|
|
81
|
+
margin: EdgeInsets.only(right: isWide(context) ? 10 : 8),
|
|
82
|
+
child: const PlayButton(),
|
|
63
|
-
|
|
83
|
+
),
|
|
64
|
-
Container(
|
|
65
|
-
margin: EdgeInsets.only(right: isWide(context) ? 10 : 8),
|
|
66
|
-
child: const PlayButton(),
|
|
67
|
-
),
|
|
68
84
|
const Menu(),
|
|
69
85
|
],
|
|
70
86
|
),
|
lib/widgets/play_button.dart
CHANGED
|
@@ -7,11 +7,10 @@ class PlayButton extends StatelessWidget {
|
|
|
7
7
|
|
|
8
8
|
@override
|
|
9
9
|
Widget build(BuildContext context) {
|
|
10
|
+
final icon = isPlaying.reactiveValue(context) ? Icons.pause_circle_filled : Icons.play_circle_fill;
|
|
10
|
-
final
|
|
11
|
+
final size = isWide(context) ? 28.0 : 42.0;
|
|
11
|
-
? Icons.pause_circle_filled
|
|
12
|
-
: Icons.play_circle_fill;
|
|
13
12
|
return IconButton(
|
|
14
|
-
icon: Icon(icon, size:
|
|
13
|
+
icon: Icon(icon, size: size),
|
|
15
14
|
onPressed: () {
|
|
16
15
|
onPlay(context);
|
|
17
16
|
},
|
lib/widgets/scaffold_menu.dart
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import "package:flutter/material.dart";
|
|
2
|
+
import "package:only_bible_app/state.dart";
|
|
3
|
+
|
|
4
|
+
class ScaffoldMenu extends StatelessWidget {
|
|
5
|
+
final Widget child;
|
|
6
|
+
|
|
7
|
+
const ScaffoldMenu({super.key, required this.child});
|
|
8
|
+
|
|
9
|
+
@override
|
|
10
|
+
Widget build(BuildContext context) {
|
|
11
|
+
return Scaffold(
|
|
12
|
+
backgroundColor: Colors.transparent,
|
|
13
|
+
body: SafeArea(
|
|
14
|
+
child: Container(
|
|
15
|
+
color: Colors.black.withOpacity(0.7),
|
|
16
|
+
margin: EdgeInsets.only(left: isWide(context) ? 250 : 0),
|
|
17
|
+
child: Container(
|
|
18
|
+
color: Theme.of(context).colorScheme.background,
|
|
19
|
+
margin: EdgeInsets.only(right: isWide(context) ? 650 : 0),
|
|
20
|
+
child: child,
|
|
21
|
+
),
|
|
22
|
+
),
|
|
23
|
+
),
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
lib/widgets/sliver_heading.dart
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import "package:flutter/material.dart";
|
|
2
|
+
|
|
3
|
+
class SliverHeading extends StatelessWidget {
|
|
4
|
+
final String title;
|
|
5
|
+
final bool showClose;
|
|
6
|
+
final double top;
|
|
7
|
+
final double bottom;
|
|
8
|
+
|
|
9
|
+
const SliverHeading({
|
|
10
|
+
super.key,
|
|
11
|
+
required this.title,
|
|
12
|
+
this.showClose = false,
|
|
13
|
+
this.top = 0,
|
|
14
|
+
this.bottom = 10,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
@override
|
|
18
|
+
Widget build(BuildContext context) {
|
|
19
|
+
return SliverToBoxAdapter(
|
|
20
|
+
child: Container(
|
|
21
|
+
margin: EdgeInsets.only(top: top, bottom: bottom, left: 20, right: 10),
|
|
22
|
+
child: Row(
|
|
23
|
+
mainAxisAlignment: MainAxisAlignment.center,
|
|
24
|
+
children: [
|
|
25
|
+
Expanded(
|
|
26
|
+
child: Text(title, style: Theme.of(context).textTheme.headlineMedium),
|
|
27
|
+
),
|
|
28
|
+
if (showClose)
|
|
29
|
+
IconButton(
|
|
30
|
+
icon: const Icon(Icons.close, size: 28),
|
|
31
|
+
onPressed: () {
|
|
32
|
+
Navigator.of(context).pop();
|
|
33
|
+
},
|
|
34
|
+
)
|
|
35
|
+
],
|
|
36
|
+
),
|
|
37
|
+
),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
lib/widgets/sliver_tile_grid.dart
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import "package:flutter/material.dart";
|
|
2
|
+
import "package:only_bible_app/state.dart";
|
|
3
|
+
|
|
4
|
+
enum ListType { small, large }
|
|
5
|
+
|
|
6
|
+
class SliverTileGrid extends StatelessWidget {
|
|
7
|
+
final ListType listType;
|
|
8
|
+
final List<Widget> children;
|
|
9
|
+
|
|
10
|
+
const SliverTileGrid({super.key, this.listType = ListType.small, required this.children});
|
|
11
|
+
|
|
12
|
+
@override
|
|
13
|
+
Widget build(BuildContext context) {
|
|
14
|
+
return SliverPadding(
|
|
15
|
+
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
16
|
+
sliver: SliverGrid.count(
|
|
17
|
+
crossAxisCount: listType == ListType.large
|
|
18
|
+
? 2
|
|
19
|
+
: isWide(context)
|
|
20
|
+
? 6
|
|
21
|
+
: 5,
|
|
22
|
+
crossAxisSpacing: 16,
|
|
23
|
+
mainAxisSpacing: 16,
|
|
24
|
+
childAspectRatio: listType == ListType.large
|
|
25
|
+
? 4
|
|
26
|
+
: isWide(context)
|
|
27
|
+
? 1.6
|
|
28
|
+
: 1.5,
|
|
29
|
+
children: children,
|
|
30
|
+
),
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
lib/widgets/verse_list.dart
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import "package:flutter/material.dart";
|
|
2
|
+
import "package:flutter_reactive_value/flutter_reactive_value.dart";
|
|
3
|
+
import "package:only_bible_app/widgets/verse_view.dart";
|
|
4
|
+
import "package:only_bible_app/state.dart";
|
|
5
|
+
|
|
6
|
+
class VerseList extends StatelessWidget {
|
|
7
|
+
const VerseList({super.key});
|
|
8
|
+
|
|
9
|
+
@override
|
|
10
|
+
Widget build(BuildContext context) {
|
|
11
|
+
final selectedBook = selectedBible.reactiveValue(context)!.books[bookIndex.value];
|
|
12
|
+
final verses = selectedBook.chapters[chapterIndex.value].verses;
|
|
13
|
+
return SelectionArea(
|
|
14
|
+
child: ListView.builder(
|
|
15
|
+
shrinkWrap: false,
|
|
16
|
+
padding: const EdgeInsets.only(
|
|
17
|
+
left: 20,
|
|
18
|
+
right: 20,
|
|
19
|
+
bottom: 20,
|
|
20
|
+
),
|
|
21
|
+
itemCount: verses.length,
|
|
22
|
+
itemBuilder: (BuildContext context, int index) {
|
|
23
|
+
final v = verses[index];
|
|
24
|
+
return Container(
|
|
25
|
+
margin: const EdgeInsets.symmetric(vertical: 6),
|
|
26
|
+
child: VerseText(index: index, text: v.text),
|
|
27
|
+
);
|
|
28
|
+
},
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
scripts/generate_audio.dart
CHANGED
|
@@ -4,8 +4,8 @@ import 'package:flutter_azure_tts/flutter_azure_tts.dart';
|
|
|
4
4
|
Future<void> convertText(Voice v, String fileName, String text) async {
|
|
5
5
|
final ttsResponse = await AzureTts.getTts(TtsParams(
|
|
6
6
|
voice: v,
|
|
7
|
-
audioFormat: AudioOutputFormat.
|
|
7
|
+
audioFormat: AudioOutputFormat.audio24khz48kBitrateMonoMp3,
|
|
8
|
-
rate: 0.
|
|
8
|
+
rate: 0.90,
|
|
9
9
|
text: text,
|
|
10
10
|
));
|
|
11
11
|
await File(fileName).writeAsBytes(ttsResponse.audio.buffer.asUint8List(), flush: true);
|
|
@@ -18,14 +18,14 @@ void main() async {
|
|
|
18
18
|
name: "",
|
|
19
19
|
displayName: "",
|
|
20
20
|
localName: "",
|
|
21
|
-
shortName: "
|
|
21
|
+
shortName: "ne-NP-SagarNeural",
|
|
22
22
|
gender: "Male",
|
|
23
|
-
locale: "
|
|
23
|
+
locale: "ne-NP",
|
|
24
24
|
sampleRateHertz: AudioOutputFormat.audio24khz48kBitrateMonoMp3,
|
|
25
25
|
voiceType: "",
|
|
26
26
|
status: "",
|
|
27
27
|
);
|
|
28
|
-
final bibleTxt = await File("./assets/bibles/
|
|
28
|
+
final bibleTxt = await File("./assets/bibles/Nepali.txt").readAsString();
|
|
29
29
|
final lines = bibleTxt.split("\n");
|
|
30
30
|
for (final line in lines) {
|
|
31
31
|
if (line.isEmpty) {
|
|
@@ -35,13 +35,16 @@ void main() async {
|
|
|
35
35
|
final chapter = line.substring(3, 6);
|
|
36
36
|
final verseNo = line.substring(7, 10);
|
|
37
37
|
final verseText = line.substring(11);
|
|
38
|
-
print("$
|
|
38
|
+
print("$book-$chapter-$verseNo.mp3");
|
|
39
|
-
final outputFilename = "./scripts/audio/$
|
|
39
|
+
final outputFilename = "./scripts/audio/Nepali/$book-$chapter-$verseNo.mp3";
|
|
40
40
|
final outFile = File(outputFilename);
|
|
41
41
|
if (outFile.existsSync() && outFile.lengthSync() > 100) {
|
|
42
42
|
continue;
|
|
43
43
|
}
|
|
44
44
|
await convertText(voice, outputFilename, verseText);
|
|
45
|
+
if (chapter == "002") {
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
45
48
|
}
|
|
46
49
|
print("finished");
|
|
47
50
|
}
|