diff --git a/assets/bibles/KJV.txt b/assets/bibles/English.txt similarity index 100% rename from assets/bibles/KJV.txt rename to assets/bibles/English.txt diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 4c4482bd..101f5d0f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -115,6 +115,8 @@ PODS: - PromisesObjC (2.3.1) - PromisesSwift (2.3.1): - PromisesObjC (= 2.3.1) + - share_plus (0.0.1): + - Flutter - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -130,6 +132,7 @@ DEPENDENCIES: - integration_test (from `.symlinks/plugins/integration_test/ios`) - just_audio (from `.symlinks/plugins/just_audio/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) SPEC REPOS: @@ -175,6 +178,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/just_audio/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" + share_plus: + :path: ".symlinks/plugins/share_plus/ios" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" @@ -208,6 +213,7 @@ SPEC CHECKSUMS: path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4 PromisesSwift: 28dca69a9c40779916ac2d6985a0192a5cb4a265 + share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028 shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 PODFILE CHECKSUM: 70d9d25280d0dd177a5f637cdb0f0b0b12c6a189 diff --git a/lib/models.dart b/lib/models.dart index dae691ae..42d910fc 100644 --- a/lib/models.dart +++ b/lib/models.dart @@ -60,17 +60,12 @@ class Chapter { } class Verse { + final int index; + final int book; + final int chapter; final String text; - final TimeRange audioRange; - - const Verse({required this.text, required this.audioRange}); -} - -class TimeRange { - final double start; - final double end; - const TimeRange({required this.start, required this.end}); + const Verse({required this.index, required this.text, required this.chapter, required this.book}); } const bookNames = >{ @@ -281,7 +276,7 @@ const bookNames = >{ }; final bibles = [ - Bible(id: 1, name: "KJV", hasAudio: false), + Bible(id: 1, name: "English", hasAudio: false), Bible(id: 2, name: "Kannada", hasAudio: true), Bible(id: 3, name: "Nepali", hasAudio: false), Bible(id: 4, name: "Hindi", hasAudio: false), @@ -304,14 +299,8 @@ List getBibleFromText(String languageCode, String text) { } var book = int.parse(line.substring(0, 2)); var chapter = int.parse(line.substring(3, 6)); - // var verseNo = line.substring(7, 10); + var verseNo = int.parse(line.substring(7, 10)); var verseText = line.substring(11); - double start = 0; - double end = 0; - // if (item.length > 4) { - // start = double.parse(item[4]); - // end = double.parse(item[5]); - // } if (books.length < book) { books.add( Book( @@ -327,8 +316,10 @@ List getBibleFromText(String languageCode, String text) { } books[book - 1].chapters[chapter - 1].verses.add( Verse( + index: verseNo - 1, text: verseText, - audioRange: TimeRange(start: start, end: end), + chapter: chapter - 1, + book: book - 1, ), ); } diff --git a/lib/screens/chapter_select_screen.dart b/lib/screens/chapter_select_screen.dart index 07878b82..5c21be08 100644 --- a/lib/screens/chapter_select_screen.dart +++ b/lib/screens/chapter_select_screen.dart @@ -12,6 +12,7 @@ class ChapterSelectScreen extends StatelessWidget { const ChapterSelectScreen({super.key, required this.selectedBookIndex, required this.book}); + // TODO: move this to app and allow to pause onChapterSelected(BuildContext context, int index) { Navigator.of(context).pushReplacement( createNoTransitionPageRoute( diff --git a/lib/state.dart b/lib/state.dart index 9b21ae36..f589fe8c 100644 --- a/lib/state.dart +++ b/lib/state.dart @@ -17,7 +17,9 @@ import "package:only_bible_app/widgets/actions_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; @@ -35,6 +37,7 @@ class AppModel extends ChangeNotifier { double textScaleFactor = 0; bool actionsShown = false; List history = []; + final box = GetStorage("only-bible-app-backup"); static AppModel of(BuildContext context) { return Provider.of(context, listen: true); @@ -202,7 +205,7 @@ class AppModel extends ChangeNotifier { class ChapterViewModel extends ChangeNotifier { final int book; final int chapter; - final List selectedVerses; + final List selectedVerses; final player = AudioPlayer(); bool isPlaying = false; @@ -235,6 +238,9 @@ class ChapterViewModel extends ChangeNotifier { } navigateBookChapter(BuildContext context, int book, int chapter, TextDirection? dir) { + if (isPlaying) { + pause(); + } AppModel.ofEvent(context).hideActions(context); Navigator.of(context).push( createSlideRoute( @@ -279,18 +285,29 @@ class ChapterViewModel extends ChangeNotifier { return selectedVerses.isNotEmpty; } - bool isVerseSelected(int i) { - return selectedVerses.contains(i); + void clearSelections(BuildContext context) { + selectedVerses.clear(); + AppModel.ofEvent(context).hideActions(context); + notifyListeners(); + } + + bool isVerseSelected(Verse v) { + return selectedVerses.any((el) => el.index == v.index); } - void onVerseSelected(BuildContext context, int i) { + 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 (selectedVerses.contains(i)) { - selectedVerses.remove(i); + if (isVerseSelected(v)) { + selectedVerses.removeWhere((it) => it.index == v.index); } else { - selectedVerses.add(i); + selectedVerses.add(v); } if (selectedVerses.isEmpty) { AppModel.ofEvent(context).hideActions(context); @@ -298,26 +315,44 @@ class ChapterViewModel extends ChangeNotifier { 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() { + final text = selectedVerses.map((e) => e.text).join("\n"); + Share.share(text); + } + + pause() async { + await player.pause(); + isPlaying = false; + notifyListeners(); + } + onPlay(BuildContext context) async { final bible = AppModel.ofEvent(context).bible; - final model = ChapterViewModel.ofEvent(context); - // if (!bible.hasAudio) { - // showError(context, "This bible version doesn't support "); - // return; - // } + if (!bible.hasAudio) { + showError( + context, + "This Bible doesn't support audio. Currently audio is only available for the Kannada Bible.", + ); + return; + } if (isPlaying) { - await player.pause(); - isPlaying = false; - notifyListeners(); + pause(); } else { isPlaying = true; notifyListeners(); for (final v in selectedVerses) { final bibleName = bible.name; - final book = (model.book + 1).toString().padLeft(2, "0"); - final chapter = (model.chapter + 1).toString().padLeft(3, "0"); - final verse = (v + 1).toString().padLeft(3, "0"); - final pathname = "$bibleName/$book-$chapter-$verse.mp3"; + 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); @@ -329,9 +364,7 @@ class ChapterViewModel extends ChangeNotifier { showError(context, "Could not play audio"); return; } finally { - await player.pause(); - isPlaying = false; - notifyListeners(); + pause(); } } } diff --git a/lib/widgets/actions_sheet.dart b/lib/widgets/actions_sheet.dart index 52304668..f36404e0 100644 --- a/lib/widgets/actions_sheet.dart +++ b/lib/widgets/actions_sheet.dart @@ -61,7 +61,9 @@ class ActionsSheet extends StatelessWidget { IconButtonText( leading: IconButton( padding: EdgeInsets.zero, - onPressed: () {}, + onPressed: () { + model.clearSelections(context); + }, icon: Icon(Icons.cancel_outlined, size: 24 + iconSize, color: iconColor), ), trailing: Text("Clear", style: bodySmall), @@ -69,7 +71,7 @@ class ActionsSheet extends StatelessWidget { IconButtonText( leading: IconButton( padding: EdgeInsets.zero, - onPressed: () {}, + onPressed: model.copyVerses, icon: Icon(Icons.copy, size: 24 + iconSize, color: iconColor), ), trailing: Text("Copy", style: bodySmall), @@ -78,13 +80,16 @@ class ActionsSheet extends StatelessWidget { leading: IconButton( padding: EdgeInsets.zero, onPressed: () { - if (app.bible.hasAudio) { - model.onPlay(context); - } + model.onPlay(context); }, icon: Icon(audioIcon, size: 34 + iconSize, color: app.bible.hasAudio ? iconColor : Colors.grey), ), - trailing: Text(audioText, style: bodySmall), + trailing: Text( + audioText, + style: bodySmall!.copyWith( + color: app.bible.hasAudio ? bodySmall!.color : Colors.grey, + ), + ), ), IconButtonText( leading: IconButton( @@ -97,7 +102,7 @@ class ActionsSheet extends StatelessWidget { IconButtonText( leading: IconButton( padding: EdgeInsets.zero, - onPressed: () {}, + onPressed: model.shareVerses, icon: Icon(Icons.share_outlined, size: 28 + iconSize, color: iconColor), ), trailing: Text("Share", style: bodySmall), diff --git a/lib/widgets/chapter_app_bar.dart b/lib/widgets/chapter_app_bar.dart index e2eb50a4..a7938848 100644 --- a/lib/widgets/chapter_app_bar.dart +++ b/lib/widgets/chapter_app_bar.dart @@ -7,6 +7,8 @@ class ChapterAppBar extends StatelessWidget implements PreferredSizeWidget { @override Size get preferredSize => const Size.fromHeight(40); + // TODO: add next/prev buttons for desktop mode + @override Widget build(BuildContext context) { final app = AppModel.of(context); diff --git a/lib/widgets/settings_sheet.dart b/lib/widgets/settings_sheet.dart index 2c6ddaeb..63d77fb4 100644 --- a/lib/widgets/settings_sheet.dart +++ b/lib/widgets/settings_sheet.dart @@ -37,7 +37,7 @@ class SettingsSheet extends StatelessWidget { tiles: [ SettingsTile.navigation( leading: const Icon(Icons.language, color: Colors.green), - title: const Text("Language"), + title: const Text("App Language"), value: const Text("English"), ), SettingsTile.navigation( diff --git a/lib/widgets/verses_view.dart b/lib/widgets/verses_view.dart index 54ce9b07..aeb074fc 100644 --- a/lib/widgets/verses_view.dart +++ b/lib/widgets/verses_view.dart @@ -42,24 +42,22 @@ class VersesView extends StatelessWidget { : textStyle, // recognizer: TapAndPanGestureRecognizer()..onDragEnd = (e) => print("Hello"), children: chapter.verses - .asMap() - .entries .map( - (e) => [ + (v) => [ WidgetSpan( child: Transform.translate( offset: const Offset(0, -2), - child: Text("${e.key + 1} ", style: Theme.of(context).textTheme.labelMedium), + child: Text("${v.index + 1} ", style: Theme.of(context).textTheme.labelMedium), ), ), TextSpan( - text: "${e.value.text}\n", - style: model.isVerseSelected(e.key) + text: "${v.text}\n", + style: model.isVerseSelected(v) ? TextStyle( backgroundColor: Theme.of(context).highlightColor, ) : null, - recognizer: TapGestureRecognizer()..onTap = () => model.onVerseSelected(context, e.key), + recognizer: TapGestureRecognizer()..onTap = () => model.onVerseSelected(context, v), ), const WidgetSpan( child: Padding( diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 1538950a..704df9b5 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -85,6 +85,8 @@ PODS: - PromisesObjC (2.3.1) - PromisesSwift (2.3.1): - PromisesObjC (= 2.3.1) + - share_plus (0.0.1): + - FlutterMacOS - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -97,6 +99,7 @@ DEPENDENCIES: - FlutterMacOS (from `Flutter/ephemeral`) - just_audio (from `Flutter/ephemeral/.symlinks/plugins/just_audio/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) + - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) SPEC REPOS: @@ -133,6 +136,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/just_audio/macos path_provider_foundation: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin + share_plus: + :path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos shared_preferences_foundation: :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin @@ -160,6 +165,7 @@ SPEC CHECKSUMS: path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4 PromisesSwift: 28dca69a9c40779916ac2d6985a0192a5cb4a265 + share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7 shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 diff --git a/pubspec.lock b/pubspec.lock index a05746ea..912e48db 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -185,6 +185,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9" + url: "https://pub.dev" + source: hosted + version: "0.3.3+4" crypto: dependency: transitive description: @@ -423,6 +431,22 @@ packages: description: flutter source: sdk version: "0.0.0" + get: + dependency: transitive + description: + name: get + sha256: "2ba20a47c8f1f233bed775ba2dd0d3ac97b4cf32fc17731b3dfc672b06b0e92a" + url: "https://pub.dev" + source: hosted + version: "4.6.5" + get_storage: + dependency: "direct main" + description: + name: get_storage + sha256: "39db1fffe779d0c22b3a744376e86febe4ade43bf65e06eab5af707dc84185a2" + url: "https://pub.dev" + source: hosted + version: "2.1.1" glob: dependency: transitive description: @@ -748,6 +772,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: "6cec740fa0943a826951223e76218df002804adb588235a8910dc3d6b0654e11" + url: "https://pub.dev" + source: hosted + version: "7.1.0" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "357412af4178d8e11d14f41723f80f12caea54cf0d5cd29af9dcdab85d58aea7" + url: "https://pub.dev" + source: hosted + version: "3.3.0" shared_preferences: dependency: "direct main" description: @@ -913,6 +953,38 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.2" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea + url: "https://pub.dev" + source: hosted + version: "2.1.3" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4 + url: "https://pub.dev" + source: hosted + version: "2.0.18" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "7967065dd2b5fccc18c653b97958fdf839c5478c28e767c61ee879f4e7882422" + url: "https://pub.dev" + source: hosted + version: "3.0.7" uuid: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 04c5b424..9e8f0d1c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,6 +24,8 @@ dependencies: firebase_performance: ^0.9.2+4 firebase_storage: ^11.2.5 settings_ui: ^2.0.2 + get_storage: ^2.1.1 + share_plus: ^7.1.0 dev_dependencies: flutter_test: @@ -53,7 +55,8 @@ flutter: flutter_launcher_icons: android: false ios: true - image_path: "assets/icon.png" + image_path: "assets/test2.png" + remove_alpha_ios: true web: generate: true background_color: "#FFFFFF"