From cfa48256efce2402dd6ca097183193f0cec9bfe0 Mon Sep 17 00:00:00 2001 From: pyrossh Date: Sun, 20 Aug 2023 18:26:24 +0530 Subject: [PATCH] Move state --- android/app/build.gradle | 14 +- lib/app.dart | 2 +- lib/main.dart | 2 +- lib/providers/app_model.dart | 235 ++++++++++++ lib/providers/chapter_view_model.dart | 187 ++++++++++ lib/screens/bible_select_screen.dart | 2 +- lib/screens/chapter_select_screen.dart | 2 +- lib/screens/chapter_view_screen.dart | 3 +- lib/state.dart | 474 ------------------------- lib/utils.dart | 62 ++++ lib/widgets/actions_sheet.dart | 4 +- lib/widgets/chapter_app_bar.dart | 4 +- lib/widgets/note_sheet.dart | 2 +- lib/widgets/scaffold_menu.dart | 2 +- lib/widgets/settings_sheet.dart | 2 +- lib/widgets/sliver_tile_grid.dart | 2 +- lib/widgets/verses_view.dart | 3 +- pubspec.lock | 64 ---- pubspec.yaml | 1 - 19 files changed, 514 insertions(+), 553 deletions(-) create mode 100644 lib/providers/app_model.dart create mode 100644 lib/providers/chapter_view_model.dart delete mode 100644 lib/state.dart create mode 100644 lib/utils.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 54d68620..6259121e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -54,7 +54,7 @@ android { // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. minSdkVersion 30 - targetSdkVersion 34 + targetSdkVersion 32 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } @@ -73,5 +73,15 @@ flutter { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" } + +configurations.all { + resolutionStrategy { + eachDependency { + if ((requested.group == "org.jetbrains.kotlin") && (requested.name.startsWith("kotlin-stdlib"))) { + useVersion("1.8.0") + } + } + } +} \ No newline at end of file diff --git a/lib/app.dart b/lib/app.dart index 55c1e7e7..415b0409 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,7 +1,7 @@ import "package:flutter/material.dart"; import "package:flutter_gen/gen_l10n/app_localizations.dart"; +import "package:only_bible_app/providers/app_model.dart"; import "package:only_bible_app/screens/chapter_view_screen.dart"; -import "package:only_bible_app/state.dart"; import "package:only_bible_app/theme.dart"; class App extends StatelessWidget { diff --git a/lib/main.dart b/lib/main.dart index b4bb2640..71d4a551 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,8 +4,8 @@ import "package:firebase_core/firebase_core.dart"; import "package:firebase_crashlytics/firebase_crashlytics.dart"; import "package:only_bible_app/firebase_options.dart"; import "package:flutter_native_splash/flutter_native_splash.dart"; -import "package:only_bible_app/state.dart"; import "package:only_bible_app/app.dart"; +import "package:only_bible_app/providers/app_model.dart"; import "package:provider/provider.dart"; void main() async { diff --git a/lib/providers/app_model.dart b/lib/providers/app_model.dart new file mode 100644 index 00000000..2fa6864a --- /dev/null +++ b/lib/providers/app_model.dart @@ -0,0 +1,235 @@ +// import "package:firebase_performance/firebase_performance.dart"; +import "package:flutter/services.dart"; +import "package:flutter/material.dart"; +import "package:only_bible_app/screens/bible_select_screen.dart"; +import "package:only_bible_app/screens/book_select_screen.dart"; +import "package:only_bible_app/models.dart"; +import "package:only_bible_app/widgets/actions_sheet.dart"; +import "package:only_bible_app/widgets/note_sheet.dart"; +import "package:only_bible_app/widgets/settings_sheet.dart"; +import "package:provider/provider.dart"; +import "package:shared_preferences/shared_preferences.dart"; +import "package:get_storage/get_storage.dart"; +import "package:only_bible_app/utils.dart"; + +class HistoryFrame { + final int book; + final int chapter; + final int? verse; + + const HistoryFrame({required this.book, required this.chapter, this.verse}); +} + +class AppModel extends ChangeNotifier { + String languageCode = "en"; + Bible bible = bibles.first; + bool darkMode = false; + bool fontBold = false; + double textScaleFactor = 0; + bool actionsShown = false; + final TextEditingController noteTextController = TextEditingController(); + List history = []; + final box = GetStorage("only-bible-app-backup"); + + static AppModel of(BuildContext context) { + return Provider.of(context, listen: true); + } + + static AppModel ofEvent(BuildContext context) { + return Provider.of(context, listen: false); + } + + save() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setInt("bibleId", bible.id); + await prefs.setBool("darkMode", darkMode); + await prefs.setBool("fontBold", fontBold); + await prefs.setDouble("textScaleFactor", textScaleFactor); + } + + Future<(int, int)> loadData() async { + final prefs = await SharedPreferences.getInstance(); + final bibleId = prefs.getInt("bibleId") ?? 1; + darkMode = prefs.getBool("darkMode") ?? false; + fontBold = prefs.getBool("fontBold") ?? false; + textScaleFactor = prefs.getDouble("textScaleFactor") ?? 1; + bible = await loadBible(bibleId); + // await Future.delayed(Duration(seconds: 3)); + final book = prefs.getInt("book") ?? 0; + final chapter = prefs.getInt("chapter") ?? 0; + updateStatusBar(); + return (book, chapter); + } + + Future loadBible(int id) async { + final selectedBible = bibles.firstWhere((it) => it.id == id); + // Trace customTrace; + // if (!isDesktop()) { + // customTrace = FirebasePerformance.instance.newTrace("loadBible"); + // await customTrace.start(); + // } + final books = await getBibleFromAsset(languageCode, selectedBible.name); + // if (!isDesktop()) { + // await customTrace.stop(); + // } + return Bible.withBooks( + id: selectedBible.id, + name: selectedBible.name, + hasAudio: selectedBible.hasAudio, + books: books, + ); + } + + changeBible(BuildContext context) { + Navigator.of(context).pushReplacement( + createNoTransitionPageRoute( + const BibleSelectScreen(), + ), + ); + } + + changeBibleFromHeader(BuildContext context) { + Navigator.of(context).push( + createNoTransitionPageRoute( + const BibleSelectScreen(), + ), + ); + } + + updateCurrentBible(BuildContext context, int id) async { + // TODO: maybe use a future as the bible needs to load + bible = await loadBible(id); + notifyListeners(); + save(); + } + + changeBook(BuildContext context) { + Navigator.of(context).push( + createNoTransitionPageRoute( + BookSelectScreen(bible: bible), + ), + ); + } + + toggleMode() async { + darkMode = !darkMode; + updateStatusBar(); + notifyListeners(); + save(); + } + + updateStatusBar() { + if (darkMode) { + SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( + systemNavigationBarColor: Color(0xFF1F1F22), + statusBarColor: Color(0xFF1F1F22), + systemNavigationBarIconBrightness: Brightness.light, + statusBarIconBrightness: Brightness.light, + )); + } else { + SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( + systemNavigationBarColor: Colors.white, + statusBarColor: Colors.white, + systemNavigationBarIconBrightness: Brightness.dark, + statusBarIconBrightness: Brightness.dark, + )); + } + } + + toggleBold() async { + fontBold = !fontBold; + notifyListeners(); + save(); + } + + increaseFont() async { + textScaleFactor += 0.1; + notifyListeners(); + save(); + } + + decreaseFont() async { + textScaleFactor -= 0.1; + notifyListeners(); + save(); + } + + showSettings(BuildContext context) { + // if (isWide(context)) { + // Navigator.of(context).push( + // createNoTransitionPageRoute( + // const ScaffoldMenu( + // backgroundColor: Color(0xFFF2F2F7), + // child: SettingsSheet(), + // ), + // ), + // ); + // } else { + showModalBottomSheet( + context: context, + isDismissible: true, + enableDrag: true, + showDragHandle: true, + useSafeArea: true, + builder: (context) => const SettingsSheet(), + ); + // } + } + + showActions(BuildContext context) { + actionsShown = true; + Scaffold.of(context).showBottomSheet( + enableDrag: false, + (context) => const ActionsSheet(), + ); + notifyListeners(); + } + + hideActions(BuildContext context) { + if (actionsShown) { + actionsShown = false; + Navigator.of(context).pop(); + notifyListeners(); + } + } + + bool hasNote(Verse v) { + return box.hasData("${v.book}:${v.chapter}:${v.index}:note"); + } + + showNoteField(BuildContext context, Verse v) { + final noteText = box.read("${v.book}:${v.chapter}:${v.index}:note") ?? ""; + noteTextController.text = noteText; + showModalBottomSheet( + context: context, + isDismissible: true, + enableDrag: true, + showDragHandle: true, + useSafeArea: true, + isScrollControlled: true, + builder: (context) => NoteSheet(verse: v), + ); + } + + saveNote(BuildContext context, Verse v) { + final note = noteTextController.text; + box.write("${v.book}:${v.chapter}:${v.index}:note", note); + box.save(); + // Close the bottom sheet + // if (!mounted) return; + // Navigator.of(context).pop(); + notifyListeners(); + hideNoteField(context); + } + + deleteNote(BuildContext context, Verse v) { + box.remove("${v.book}:${v.chapter}:${v.index}:note"); + box.save(); + notifyListeners(); + hideNoteField(context); + } + + hideNoteField(BuildContext context) { + Navigator.of(context).pop(); + } +} diff --git a/lib/providers/chapter_view_model.dart b/lib/providers/chapter_view_model.dart new file mode 100644 index 00000000..f768b750 --- /dev/null +++ b/lib/providers/chapter_view_model.dart @@ -0,0 +1,187 @@ +import "dart:developer"; +import "package:firebase_crashlytics/firebase_crashlytics.dart"; +import "package:firebase_storage/firebase_storage.dart"; +import "package:flutter/services.dart"; +import "package:flutter/material.dart"; +import "package:just_audio/just_audio.dart"; +import "package:only_bible_app/screens/chapter_view_screen.dart"; +import "package:only_bible_app/dialog.dart"; +import "package:only_bible_app/models.dart"; +import "package:provider/provider.dart"; +import "package:share_plus/share_plus.dart"; +import "package:shared_preferences/shared_preferences.dart"; +import "package:only_bible_app/utils.dart"; +import "package:only_bible_app/providers/app_model.dart"; + +class ChapterViewModel extends ChangeNotifier { + final int book; + final int chapter; + final List selectedVerses; + final player = AudioPlayer(); + bool isPlaying = false; + + static ChapterViewModel of(BuildContext context) { + return Provider.of(context, listen: true); + } + + static ChapterViewModel ofEvent(BuildContext context) { + return Provider.of(context, listen: false); + } + + static Book selectedBook(BuildContext context) { + final model = of(context); + return AppModel.of(context).bible.books[model.book]; + } + + static Chapter selectedChapter(BuildContext context) { + final model = of(context); + return AppModel.of(context).bible.books[model.book].chapters[model.chapter]; + } + + ChapterViewModel({required this.book, required this.chapter, required this.selectedVerses}) { + save(book, chapter); + } + + save(int book, int chapter) async { + final prefs = await SharedPreferences.getInstance(); + prefs.setInt("book", book); + prefs.setInt("chapter", chapter); + } + + navigateBookChapter(BuildContext context, int book, int chapter, TextDirection? dir) { + if (isPlaying) { + pause(); + } + AppModel.ofEvent(context).hideActions(context); + Navigator.of(context).push( + createSlideRoute( + context: context, + slideDir: dir, + page: ChapterViewScreen(book: book, chapter: chapter), + ), + ); + } + + onNext(BuildContext context, int book, int chapter) { + final selectedBible = AppModel.ofEvent(context).bible; + final selectedBook = selectedBible.books[book]; + if (selectedBook.chapters.length > chapter + 1) { + navigateBookChapter(context, selectedBook.index, chapter + 1, TextDirection.ltr); + } else { + if (selectedBook.index + 1 < selectedBible.books.length) { + final nextBook = selectedBible.books[selectedBook.index + 1]; + navigateBookChapter(context, nextBook.index, 0, TextDirection.ltr); + } + } + } + + onPrevious(BuildContext context, int book, int chapter) { + final selectedBible = AppModel.ofEvent(context).bible; + final selectedBook = selectedBible.books[book]; + if (chapter - 1 >= 0) { + // if (Navigator.of(context).canPop()) { + // Navigator.of(context).pop(); + // } else { + navigateBookChapter(context, selectedBook.index, chapter - 1, TextDirection.rtl); + // } + } else { + if (selectedBook.index - 1 >= 0) { + final prevBook = selectedBible.books[selectedBook.index - 1]; + navigateBookChapter(context, prevBook.index, prevBook.chapters.length - 1, TextDirection.rtl); + } + } + } + + bool hasSelectedVerses() { + return selectedVerses.isNotEmpty; + } + + void clearSelections(BuildContext context) { + selectedVerses.clear(); + AppModel.ofEvent(context).hideActions(context); + notifyListeners(); + } + + bool isVerseSelected(Verse v) { + return selectedVerses.any((el) => el.index == v.index); + } + + bool isVerseHighlighted(BuildContext context) { + // box.read("${book}:${chapter}:${verse}", "color"); + return false; + } + + void onVerseSelected(BuildContext context, Verse v) { + if (selectedVerses.isEmpty) { + AppModel.ofEvent(context).showActions(context); + } + if (isVerseSelected(v)) { + selectedVerses.removeWhere((it) => it.index == v.index); + } else { + selectedVerses.add(v); + } + if (selectedVerses.isEmpty) { + AppModel.ofEvent(context).hideActions(context); + } + notifyListeners(); + } + + void copyVerses() { + final text = selectedVerses.map((e) => e.text).join("\n"); + Clipboard.setData(ClipboardData(text: text)); + // maybe close the action menu or show a snackbar on iOS (android already does this) + // ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Copied to clipboard"))); + } + + void shareVerses(BuildContext context) { + final bible = AppModel.ofEvent(context).bible; + final name = bible.books[selectedVerses.first.book].name; + final chapter = selectedVerses.first.chapter + 1; + final title = "$name $chapter: ${selectedVerses.map((e) => e.index + 1).join(", ")}"; + final text = selectedVerses.map((e) => e.text).join("\n"); + Share.share("$title\n$text", subject: title); + } + + pause() async { + await player.pause(); + isPlaying = false; + notifyListeners(); + } + + onPlay(BuildContext context) async { + final bible = AppModel.ofEvent(context).bible; + if (!bible.hasAudio) { + showError( + context, + "This Bible doesn't support audio. Currently audio is only available for the Kannada Bible.", + ); + return; + } + if (isPlaying) { + pause(); + } else { + isPlaying = true; + notifyListeners(); + for (final v in selectedVerses) { + final bibleName = bible.name; + final book = (v.book + 1).toString().padLeft(2, "0"); + final chapter = (v.chapter + 1).toString().padLeft(3, "0"); + final verseNo = (v.index + 1).toString().padLeft(3, "0"); + final pathname = "$bibleName/$book-$chapter-$verseNo.mp3"; + try { + final url = await FirebaseStorage.instance.ref(pathname).getDownloadURL(); + await player.setUrl(url); + await player.play(); + await player.stop(); + } catch (err) { + log("Could not play audio", name: "play", error: (err.toString(), pathname)); + FirebaseCrashlytics.instance.recordFlutterError(FlutterErrorDetails(exception: (err.toString(), pathname))); + showError(context, "Could not play audio"); + return; + } finally { + pause(); + } + } + } + } +} \ No newline at end of file diff --git a/lib/screens/bible_select_screen.dart b/lib/screens/bible_select_screen.dart index e13c04a8..d7f54538 100644 --- a/lib/screens/bible_select_screen.dart +++ b/lib/screens/bible_select_screen.dart @@ -1,6 +1,6 @@ import "package:flutter/material.dart"; -import "package:only_bible_app/state.dart"; import "package:only_bible_app/models.dart"; +import "package:only_bible_app/providers/app_model.dart"; import "package:only_bible_app/widgets/scaffold_menu.dart"; import "package:only_bible_app/widgets/sliver_heading.dart"; import "package:only_bible_app/widgets/sliver_tile_grid.dart"; diff --git a/lib/screens/chapter_select_screen.dart b/lib/screens/chapter_select_screen.dart index 5c21be08..4d5d6d07 100644 --- a/lib/screens/chapter_select_screen.dart +++ b/lib/screens/chapter_select_screen.dart @@ -1,6 +1,6 @@ import "package:flutter/material.dart"; import "package:only_bible_app/models.dart"; -import "package:only_bible_app/state.dart"; +import "package:only_bible_app/utils.dart"; import "package:only_bible_app/widgets/scaffold_menu.dart"; import "package:only_bible_app/widgets/sliver_tile_grid.dart"; import "package:only_bible_app/widgets/sliver_heading.dart"; diff --git a/lib/screens/chapter_view_screen.dart b/lib/screens/chapter_view_screen.dart index 6584a509..c7fd6e2b 100644 --- a/lib/screens/chapter_view_screen.dart +++ b/lib/screens/chapter_view_screen.dart @@ -1,6 +1,7 @@ import "package:flutter/material.dart"; +import "package:only_bible_app/providers/chapter_view_model.dart"; +import "package:only_bible_app/utils.dart"; import "package:only_bible_app/widgets/chapter_app_bar.dart"; -import "package:only_bible_app/state.dart"; import "package:only_bible_app/widgets/sidebar.dart"; import "package:only_bible_app/widgets/verses_view.dart"; import "package:provider/provider.dart"; diff --git a/lib/state.dart b/lib/state.dart deleted file mode 100644 index 14521da8..00000000 --- a/lib/state.dart +++ /dev/null @@ -1,474 +0,0 @@ -import "dart:convert"; -import "dart:developer"; -import "package:firebase_crashlytics/firebase_crashlytics.dart"; -import "package:firebase_storage/firebase_storage.dart"; - -// import "package:firebase_performance/firebase_performance.dart"; -import "package:flutter/foundation.dart" show defaultTargetPlatform, TargetPlatform; -import "package:flutter/services.dart"; -import "package:flutter/material.dart"; -import "package:just_audio/just_audio.dart"; -import "package:only_bible_app/screens/bible_select_screen.dart"; -import "package:only_bible_app/screens/book_select_screen.dart"; -import "package:only_bible_app/screens/chapter_view_screen.dart"; -import "package:only_bible_app/dialog.dart"; -import "package:only_bible_app/models.dart"; -import "package:only_bible_app/widgets/actions_sheet.dart"; -import "package:only_bible_app/widgets/note_sheet.dart"; -import "package:only_bible_app/widgets/scaffold_menu.dart"; -import "package:only_bible_app/widgets/settings_sheet.dart"; -import "package:provider/provider.dart"; -import "package:share_plus/share_plus.dart"; -import "package:shared_preferences/shared_preferences.dart"; -import "package:get_storage/get_storage.dart"; - -class HistoryFrame { - final int book; - final int chapter; - final int? verse; - - const HistoryFrame({required this.book, required this.chapter, this.verse}); -} - -class AppModel extends ChangeNotifier { - String languageCode = "en"; - Bible bible = bibles.first; - bool darkMode = false; - bool fontBold = false; - double textScaleFactor = 0; - bool actionsShown = false; - final TextEditingController noteTextController = TextEditingController(); - List history = []; - final box = GetStorage("only-bible-app-backup"); - - static AppModel of(BuildContext context) { - return Provider.of(context, listen: true); - } - - static AppModel ofEvent(BuildContext context) { - return Provider.of(context, listen: false); - } - - save() async { - final prefs = await SharedPreferences.getInstance(); - await prefs.setInt("bibleId", bible.id); - await prefs.setBool("darkMode", darkMode); - await prefs.setBool("fontBold", fontBold); - await prefs.setDouble("textScaleFactor", textScaleFactor); - } - - Future<(int, int)> loadData() async { - final prefs = await SharedPreferences.getInstance(); - final bibleId = prefs.getInt("bibleId") ?? 1; - darkMode = prefs.getBool("darkMode") ?? false; - fontBold = prefs.getBool("fontBold") ?? false; - textScaleFactor = prefs.getDouble("textScaleFactor") ?? 1; - bible = await loadBible(bibleId); - // await Future.delayed(Duration(seconds: 3)); - final book = prefs.getInt("book") ?? 0; - final chapter = prefs.getInt("chapter") ?? 0; - updateStatusBar(); - return (book, chapter); - } - - Future loadBible(int id) async { - final selectedBible = bibles.firstWhere((it) => it.id == id); - // Trace customTrace; - // if (!isDesktop()) { - // customTrace = FirebasePerformance.instance.newTrace("loadBible"); - // await customTrace.start(); - // } - final books = await getBibleFromAsset(languageCode, selectedBible.name); - // if (!isDesktop()) { - // await customTrace.stop(); - // } - return Bible.withBooks( - id: selectedBible.id, - name: selectedBible.name, - hasAudio: selectedBible.hasAudio, - books: books, - ); - } - - changeBible(BuildContext context) { - Navigator.of(context).pushReplacement( - createNoTransitionPageRoute( - const BibleSelectScreen(), - ), - ); - } - - changeBibleFromHeader(BuildContext context) { - Navigator.of(context).push( - createNoTransitionPageRoute( - const BibleSelectScreen(), - ), - ); - } - - updateCurrentBible(BuildContext context, int id) async { - // TODO: maybe use a future as the bible needs to load - bible = await loadBible(id); - notifyListeners(); - save(); - } - - changeBook(BuildContext context) { - Navigator.of(context).push( - createNoTransitionPageRoute( - BookSelectScreen(bible: bible), - ), - ); - } - - toggleMode() async { - darkMode = !darkMode; - updateStatusBar(); - notifyListeners(); - save(); - } - - updateStatusBar() { - if (darkMode) { - SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( - systemNavigationBarColor: Color(0xFF1F1F22), - statusBarColor: Color(0xFF1F1F22), - systemNavigationBarIconBrightness: Brightness.light, - statusBarIconBrightness: Brightness.light, - )); - } else { - SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( - systemNavigationBarColor: Colors.white, - statusBarColor: Colors.white, - systemNavigationBarIconBrightness: Brightness.dark, - statusBarIconBrightness: Brightness.dark, - )); - } - } - - toggleBold() async { - fontBold = !fontBold; - notifyListeners(); - save(); - } - - increaseFont() async { - textScaleFactor += 0.1; - notifyListeners(); - save(); - } - - decreaseFont() async { - textScaleFactor -= 0.1; - notifyListeners(); - save(); - } - - showSettings(BuildContext context) { - // if (isWide(context)) { - // Navigator.of(context).push( - // createNoTransitionPageRoute( - // const ScaffoldMenu( - // backgroundColor: Color(0xFFF2F2F7), - // child: SettingsSheet(), - // ), - // ), - // ); - // } else { - showModalBottomSheet( - context: context, - isDismissible: true, - enableDrag: true, - showDragHandle: true, - useSafeArea: true, - builder: (context) => const SettingsSheet(), - ); - // } - } - - showActions(BuildContext context) { - actionsShown = true; - Scaffold.of(context).showBottomSheet( - enableDrag: false, - (context) => const ActionsSheet(), - ); - notifyListeners(); - } - - hideActions(BuildContext context) { - if (actionsShown) { - actionsShown = false; - Navigator.of(context).pop(); - notifyListeners(); - } - } - - bool hasNote(Verse v) { - return box.hasData("${v.book}:${v.chapter}:${v.index}:note"); - } - - showNoteField(BuildContext context, Verse v) { - final noteText = box.read("${v.book}:${v.chapter}:${v.index}:note") ?? ""; - noteTextController.text = noteText; - showModalBottomSheet( - context: context, - isDismissible: true, - enableDrag: true, - showDragHandle: true, - useSafeArea: true, - isScrollControlled: true, - builder: (context) => NoteSheet(verse: v), - ); - } - - saveNote(BuildContext context, Verse v) { - final note = noteTextController.text; - box.write("${v.book}:${v.chapter}:${v.index}:note", note); - box.save(); - // Close the bottom sheet - // if (!mounted) return; - // Navigator.of(context).pop(); - notifyListeners(); - hideNoteField(context); - } - - deleteNote(BuildContext context, Verse v) { - box.remove("${v.book}:${v.chapter}:${v.index}:note"); - box.save(); - notifyListeners(); - hideNoteField(context); - } - - hideNoteField(BuildContext context) { - Navigator.of(context).pop(); - } -} - -class ChapterViewModel extends ChangeNotifier { - final int book; - final int chapter; - final List selectedVerses; - final player = AudioPlayer(); - bool isPlaying = false; - - static ChapterViewModel of(BuildContext context) { - return Provider.of(context, listen: true); - } - - static ChapterViewModel ofEvent(BuildContext context) { - return Provider.of(context, listen: false); - } - - static Book selectedBook(BuildContext context) { - final model = of(context); - return AppModel.of(context).bible.books[model.book]; - } - - static Chapter selectedChapter(BuildContext context) { - final model = of(context); - return AppModel.of(context).bible.books[model.book].chapters[model.chapter]; - } - - ChapterViewModel({required this.book, required this.chapter, required this.selectedVerses}) { - save(book, chapter); - } - - save(int book, int chapter) async { - final prefs = await SharedPreferences.getInstance(); - prefs.setInt("book", book); - prefs.setInt("chapter", chapter); - } - - navigateBookChapter(BuildContext context, int book, int chapter, TextDirection? dir) { - if (isPlaying) { - pause(); - } - AppModel.ofEvent(context).hideActions(context); - Navigator.of(context).push( - createSlideRoute( - context: context, - slideDir: dir, - page: ChapterViewScreen(book: book, chapter: chapter), - ), - ); - } - - onNext(BuildContext context, int book, int chapter) { - final selectedBible = AppModel.ofEvent(context).bible; - final selectedBook = selectedBible.books[book]; - if (selectedBook.chapters.length > chapter + 1) { - navigateBookChapter(context, selectedBook.index, chapter + 1, TextDirection.ltr); - } else { - if (selectedBook.index + 1 < selectedBible.books.length) { - final nextBook = selectedBible.books[selectedBook.index + 1]; - navigateBookChapter(context, nextBook.index, 0, TextDirection.ltr); - } - } - } - - onPrevious(BuildContext context, int book, int chapter) { - final selectedBible = AppModel.ofEvent(context).bible; - final selectedBook = selectedBible.books[book]; - if (chapter - 1 >= 0) { - // if (Navigator.of(context).canPop()) { - // Navigator.of(context).pop(); - // } else { - navigateBookChapter(context, selectedBook.index, chapter - 1, TextDirection.rtl); - // } - } else { - if (selectedBook.index - 1 >= 0) { - final prevBook = selectedBible.books[selectedBook.index - 1]; - navigateBookChapter(context, prevBook.index, prevBook.chapters.length - 1, TextDirection.rtl); - } - } - } - - bool hasSelectedVerses() { - return selectedVerses.isNotEmpty; - } - - void clearSelections(BuildContext context) { - selectedVerses.clear(); - AppModel.ofEvent(context).hideActions(context); - notifyListeners(); - } - - bool isVerseSelected(Verse v) { - return selectedVerses.any((el) => el.index == v.index); - } - - bool isVerseHighlighted(BuildContext context) { - // box.read("${book}:${chapter}:${verse}", "color"); - return false; - } - - void onVerseSelected(BuildContext context, Verse v) { - if (selectedVerses.isEmpty) { - AppModel.ofEvent(context).showActions(context); - } - if (isVerseSelected(v)) { - selectedVerses.removeWhere((it) => it.index == v.index); - } else { - selectedVerses.add(v); - } - if (selectedVerses.isEmpty) { - AppModel.ofEvent(context).hideActions(context); - } - notifyListeners(); - } - - void copyVerses() { - final text = selectedVerses.map((e) => e.text).join("\n"); - Clipboard.setData(ClipboardData(text: text)); - // maybe close the action menu or show a snackbar - // ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Email address copied to clipboard"))); - } - - void shareVerses(BuildContext context) { - final bible = AppModel.ofEvent(context).bible; - final name = bible.books[selectedVerses.first.book].name; - final chapter = selectedVerses.first.chapter + 1; - final title = "$name $chapter: ${selectedVerses.map((e) => e.index + 1).join(", ")}"; - final text = selectedVerses.map((e) => e.text).join("\n"); - Share.share("$title\n$text", subject: title); - } - - pause() async { - await player.pause(); - isPlaying = false; - notifyListeners(); - } - - onPlay(BuildContext context) async { - final bible = AppModel.ofEvent(context).bible; - if (!bible.hasAudio) { - showError( - context, - "This Bible doesn't support audio. Currently audio is only available for the Kannada Bible.", - ); - return; - } - if (isPlaying) { - pause(); - } else { - isPlaying = true; - notifyListeners(); - for (final v in selectedVerses) { - final bibleName = bible.name; - final book = (v.book + 1).toString().padLeft(2, "0"); - final chapter = (v.chapter + 1).toString().padLeft(3, "0"); - final verseNo = (v.index + 1).toString().padLeft(3, "0"); - final pathname = "$bibleName/$book-$chapter-$verseNo.mp3"; - try { - final url = await FirebaseStorage.instance.ref(pathname).getDownloadURL(); - await player.setUrl(url); - await player.play(); - await player.stop(); - } catch (err) { - log("Could not play audio", name: "play", error: (err.toString(), pathname)); - FirebaseCrashlytics.instance.recordFlutterError(FlutterErrorDetails(exception: (err.toString(), pathname))); - showError(context, "Could not play audio"); - return; - } finally { - pause(); - } - } - } - } -} - -bool isDesktop() { - return defaultTargetPlatform == TargetPlatform.macOS || - defaultTargetPlatform == TargetPlatform.windows || - defaultTargetPlatform == TargetPlatform.linux; -} - -bool isIOS() { - return defaultTargetPlatform == TargetPlatform.iOS; -} - -bool isWide(BuildContext context) { - if (defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS) { - return false; - } - final width = MediaQuery.of(context).size.width; - return width > 600; -} - -createNoTransitionPageRoute(Widget page) { - return PageRouteBuilder( - opaque: false, - transitionDuration: Duration.zero, - reverseTransitionDuration: Duration.zero, - pageBuilder: (context, _, __) => page, - ); -} - -createSlideRoute({required BuildContext context, TextDirection? slideDir, required Widget page}) { - if (isWide(context) || slideDir == null) { - return PageRouteBuilder( - pageBuilder: (context, _, __) { - return page; - }, - ); - } - return PageRouteBuilder( - pageBuilder: (context, animation, secondaryAnimation) => page, - transitionsBuilder: (context, animation, secondaryAnimation, child) { - const begin = Offset(1.0, 0.0); - const end = Offset.zero; - const curve = Curves.ease; - var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve)); - return SlideTransition( - textDirection: slideDir, - position: animation.drive(tween), - child: child, - ); - }, - ); -} - -getBibleFromAsset(String languageCode, String file) async { - final bytes = await rootBundle.load("assets/bibles/$file.txt"); - return getBibleFromText(languageCode, utf8.decode(bytes.buffer.asUint8List(), allowMalformed: false)); -} diff --git a/lib/utils.dart b/lib/utils.dart new file mode 100644 index 00000000..ac874eb9 --- /dev/null +++ b/lib/utils.dart @@ -0,0 +1,62 @@ +import "dart:convert"; +import "package:flutter/foundation.dart" show defaultTargetPlatform, TargetPlatform; +import "package:flutter/material.dart"; +import "package:flutter/services.dart"; +import "package:only_bible_app/models.dart"; + + +bool isDesktop() { + return defaultTargetPlatform == TargetPlatform.macOS || + defaultTargetPlatform == TargetPlatform.windows || + defaultTargetPlatform == TargetPlatform.linux; +} + +bool isIOS() { + return defaultTargetPlatform == TargetPlatform.iOS; +} + +bool isWide(BuildContext context) { + if (defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS) { + return false; + } + final width = MediaQuery.of(context).size.width; + return width > 600; +} + +createNoTransitionPageRoute(Widget page) { + return PageRouteBuilder( + opaque: false, + transitionDuration: Duration.zero, + reverseTransitionDuration: Duration.zero, + pageBuilder: (context, _, __) => page, + ); +} + +createSlideRoute({required BuildContext context, TextDirection? slideDir, required Widget page}) { + if (isWide(context) || slideDir == null) { + return PageRouteBuilder( + pageBuilder: (context, _, __) { + return page; + }, + ); + } + return PageRouteBuilder( + pageBuilder: (context, animation, secondaryAnimation) => page, + transitionsBuilder: (context, animation, secondaryAnimation, child) { + const begin = Offset(1.0, 0.0); + const end = Offset.zero; + const curve = Curves.ease; + var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve)); + return SlideTransition( + textDirection: slideDir, + position: animation.drive(tween), + child: child, + ); + }, + ); +} + +getBibleFromAsset(String languageCode, String file) async { + final bytes = await rootBundle.load("assets/bibles/$file.txt"); + return getBibleFromText(languageCode, utf8.decode(bytes.buffer.asUint8List(), allowMalformed: false)); +} \ No newline at end of file diff --git a/lib/widgets/actions_sheet.dart b/lib/widgets/actions_sheet.dart index 5738e7a9..728e7cdd 100644 --- a/lib/widgets/actions_sheet.dart +++ b/lib/widgets/actions_sheet.dart @@ -1,5 +1,7 @@ import "package:flutter/material.dart"; -import "package:only_bible_app/state.dart"; +import "package:only_bible_app/providers/app_model.dart"; +import "package:only_bible_app/providers/chapter_view_model.dart"; +import "package:only_bible_app/utils.dart"; import "package:only_bible_app/widgets/highlight_button.dart"; import "package:only_bible_app/widgets/icon_button_text.dart"; diff --git a/lib/widgets/chapter_app_bar.dart b/lib/widgets/chapter_app_bar.dart index 26f9ee18..414f85bd 100644 --- a/lib/widgets/chapter_app_bar.dart +++ b/lib/widgets/chapter_app_bar.dart @@ -1,5 +1,7 @@ import "package:flutter/material.dart"; -import "package:only_bible_app/state.dart"; +import "package:only_bible_app/providers/app_model.dart"; +import "package:only_bible_app/providers/chapter_view_model.dart"; +import "package:only_bible_app/utils.dart"; class ChapterAppBar extends StatelessWidget implements PreferredSizeWidget { const ChapterAppBar({super.key}); diff --git a/lib/widgets/note_sheet.dart b/lib/widgets/note_sheet.dart index 201d26ec..6b24053c 100644 --- a/lib/widgets/note_sheet.dart +++ b/lib/widgets/note_sheet.dart @@ -1,6 +1,6 @@ import "package:flutter/material.dart"; import "package:only_bible_app/models.dart"; -import "package:only_bible_app/state.dart"; +import "package:only_bible_app/providers/app_model.dart"; import "package:only_bible_app/widgets/modal_button.dart"; class NoteSheet extends StatelessWidget { diff --git a/lib/widgets/scaffold_menu.dart b/lib/widgets/scaffold_menu.dart index 0d66c85a..c12ea007 100644 --- a/lib/widgets/scaffold_menu.dart +++ b/lib/widgets/scaffold_menu.dart @@ -1,5 +1,5 @@ import "package:flutter/material.dart"; -import "package:only_bible_app/state.dart"; +import "package:only_bible_app/utils.dart"; class ScaffoldMenu extends StatelessWidget { final Widget child; diff --git a/lib/widgets/settings_sheet.dart b/lib/widgets/settings_sheet.dart index 63d77fb4..d3f37b88 100644 --- a/lib/widgets/settings_sheet.dart +++ b/lib/widgets/settings_sheet.dart @@ -1,5 +1,5 @@ import "package:flutter/material.dart"; -import "package:only_bible_app/state.dart"; +import "package:only_bible_app/providers/app_model.dart"; import "package:settings_ui/settings_ui.dart"; class SettingsSheet extends StatelessWidget { diff --git a/lib/widgets/sliver_tile_grid.dart b/lib/widgets/sliver_tile_grid.dart index be5b3f3d..128764d8 100644 --- a/lib/widgets/sliver_tile_grid.dart +++ b/lib/widgets/sliver_tile_grid.dart @@ -1,5 +1,5 @@ import "package:flutter/material.dart"; -import "package:only_bible_app/state.dart"; +import "package:only_bible_app/utils.dart"; enum ListType { small, diff --git a/lib/widgets/verses_view.dart b/lib/widgets/verses_view.dart index dbe5147a..62a0500c 100644 --- a/lib/widgets/verses_view.dart +++ b/lib/widgets/verses_view.dart @@ -1,7 +1,8 @@ import "package:flutter/gestures.dart"; import "package:flutter/material.dart"; import "package:flutter_swipe_detector/flutter_swipe_detector.dart"; -import "package:only_bible_app/state.dart"; +import "package:only_bible_app/providers/app_model.dart"; +import "package:only_bible_app/providers/chapter_view_model.dart"; import "package:provider/provider.dart"; class VersesView extends StatelessWidget { diff --git a/pubspec.lock b/pubspec.lock index 96fb77e0..912e48db 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -257,70 +257,6 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.4" - file_selector: - dependency: "direct main" - description: - name: file_selector - sha256: "59b35aa4af7988be7ec88f9ddaa6c71c5b54bf0f8b35009389d9343b10e9c3af" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - file_selector_android: - dependency: transitive - description: - name: file_selector_android - sha256: "43e5c719f671b9181bef1bf2851135c3ad993a9a6c804a4ccb07579cfee84e34" - url: "https://pub.dev" - source: hosted - version: "0.5.0+2" - file_selector_ios: - dependency: transitive - description: - name: file_selector_ios - sha256: "507af301b21b1dbb6fd0615ba21190b2b4574edb672929f32ce7f610c40a9bd9" - url: "https://pub.dev" - source: hosted - version: "0.5.1+5" - file_selector_linux: - dependency: transitive - description: - name: file_selector_linux - sha256: "770eb1ab057b5ae4326d1c24cc57710758b9a46026349d021d6311bd27580046" - url: "https://pub.dev" - source: hosted - version: "0.9.2" - file_selector_macos: - dependency: transitive - description: - name: file_selector_macos - sha256: "4ada532862917bf16e3adb3891fe3a5917a58bae03293e497082203a80909412" - url: "https://pub.dev" - source: hosted - version: "0.9.3+1" - file_selector_platform_interface: - dependency: transitive - description: - name: file_selector_platform_interface - sha256: "412705a646a0ae90f33f37acfae6a0f7cbc02222d6cd34e479421c3e74d3853c" - url: "https://pub.dev" - source: hosted - version: "2.6.0" - file_selector_web: - dependency: transitive - description: - name: file_selector_web - sha256: e292740c469df0aeeaba0895bf622bea351a05e87d22864c826bf21c4780e1d7 - url: "https://pub.dev" - source: hosted - version: "0.9.2" - file_selector_windows: - dependency: transitive - description: - name: file_selector_windows - sha256: "1372760c6b389842b77156203308940558a2817360154084368608413835fc26" - url: "https://pub.dev" - source: hosted - version: "0.9.3" firebase_core: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index f3b77daf..9e8f0d1c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -26,7 +26,6 @@ dependencies: settings_ui: ^2.0.2 get_storage: ^2.1.1 share_plus: ^7.1.0 - file_selector: ^1.0.0 # need this to fix android build dev_dependencies: flutter_test: