diff --git a/lib/core/constants.dart b/lib/core/constants.dart index c40ef42..65cb2c9 100644 --- a/lib/core/constants.dart +++ b/lib/core/constants.dart @@ -50,6 +50,14 @@ Future mediaDirectory() async { return p.join(await appDirectory(), 'media'); } +Future settingsFile() async { + return p.join(await settingsDir(),'settings.json'); +} + +Future settingsDir() async { + return p.join(await appDirectory(), 'settings'); +} + Future photosDirectory() async { return p.join(await mediaDirectory(), 'photos'); } diff --git a/lib/core/file_utils.dart b/lib/core/file_utils.dart index f9456d9..f20130d 100644 --- a/lib/core/file_utils.dart +++ b/lib/core/file_utils.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:io'; import 'package:file_picker/file_picker.dart'; @@ -19,6 +20,8 @@ class FileUtils { Directory(await songsDirectory()).create(recursive: false); Directory(await playlistsDirectory()).create(recursive: false); Directory(await biblesDirectory()).create(recursive: false); + + File(await settingsFile()).createSync(recursive: true); } static Future importPhotos(List files) async { @@ -124,4 +127,8 @@ class FileUtils { File(p.join(await playlistsDirectory(), fileName)).writeAsStringSync(Playlist.addNew(fileName).toJson()); } + + static void saveSettings(Map settings) async { + File(await settingsFile()).writeAsStringSync(const JsonEncoder.withIndent(' ').convert(settings)); + } } diff --git a/lib/core/providers_declaration.dart b/lib/core/providers_declaration.dart index 337cd85..b4891c2 100644 --- a/lib/core/providers_declaration.dart +++ b/lib/core/providers_declaration.dart @@ -1,4 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freecps/core/constants.dart'; +import 'package:freecps/core/file_utils.dart'; +import 'package:freecps/notifiers/settings_notifier.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import '../models/playlist_model.dart'; @@ -42,7 +45,7 @@ final verseListControllerProvider = StateNotifierProvider((ref) { - return PlaylistNotifier(); + return PlaylistNotifier(ref); }); final slidePanelTitleProvider = StateProvider( @@ -50,3 +53,42 @@ final slidePanelTitleProvider = StateProvider( return ''; }, ); + +final settingsProvider = StateNotifierProvider>((ref) { + String file = ref.watch(directoriesProvider)['settingsFile']!; + + return SettingsNotifier(file); +}); + +final directoriesProvider = StateProvider>((ref) { + return {}; +}); + +final initProvider = FutureProvider((ref) async { + bool? initialized; + + try { + await FileUtils.initializeDirectories(); + + ref.read(directoriesProvider.notifier).state = { + 'photosDir': await photosDirectory(), + 'videosDir': await videosDirectory(), + 'appDir': await appDirectory(), + 'biblesDir': await biblesDirectory(), + 'songsDir': await songsDirectory(), + 'mediaDir': await mediaDirectory(), + 'settingsDir': await settingsDir(), + 'settingsFile': await settingsFile(), + 'photoThumbnailsDir': await photoThumbnailsDirectory(), + 'playlistDir': await playlistsDirectory(), + }; + + await Future.delayed(const Duration(seconds: 1)); + + initialized = true; + } catch (e) { + // + } + + return initialized; +}); diff --git a/lib/main.dart b/lib/main.dart index 4d02eac..e4edf6c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,10 +1,7 @@ import 'package:dart_vlc/dart_vlc.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:hive/hive.dart'; -import 'package:path_provider/path_provider.dart'; -import 'core/file_utils.dart'; import 'windows/main_window.dart'; import 'windows/projection_window.dart'; import 'package:window_manager/window_manager.dart'; @@ -15,9 +12,7 @@ void main(List args) async { if (args.isEmpty) { // TODO: put minimum size in main window // TODO: put in initialize window before main window? - FileUtils.initializeDirectories(); - Hive.init(await getApplicationSupportDirectory().then((value) => value.path)); - await Hive.openBox('settings'); + runApp( ProviderScope( child: MaterialApp( diff --git a/lib/models/playlist_model.dart b/lib/models/playlist_model.dart index 4c00264..ded484e 100644 --- a/lib/models/playlist_model.dart +++ b/lib/models/playlist_model.dart @@ -91,7 +91,7 @@ class Playlist { }; } - String toJson() => json.encode(toMap()); + String toJson() => const JsonEncoder.withIndent(' ').convert(toMap()); Playlist copyWith({ String? title, diff --git a/lib/notifiers/playlist_notifier.dart b/lib/notifiers/playlist_notifier.dart index 2274691..17fcada 100644 --- a/lib/notifiers/playlist_notifier.dart +++ b/lib/notifiers/playlist_notifier.dart @@ -2,7 +2,7 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:hive/hive.dart'; +import 'package:freecps/core/providers_declaration.dart'; import 'package:path/path.dart'; import '../core/constants.dart'; @@ -10,22 +10,24 @@ import '../core/file_utils.dart'; import '../models/playlist_model.dart'; class PlaylistNotifier extends StateNotifier { - PlaylistNotifier() : super(Playlist.empty()) { + PlaylistNotifier(this.ref) : super(Playlist.empty()) { _init(); } late String playlistDir; late String songsDir; + StateNotifierProviderRef ref; select(String fileName) async { String fileDir = await FileUtils.getPlaylistPath(fileName); - try { state = Playlist.fromJson(File(fileDir).readAsStringSync(), songsDir); // save selected playlist filename to hive database - Hive.box('settings').put('playlist', fileName); + // Hive.box('settings').put('playlist', fileName); + + ref.read(settingsProvider.notifier).update('playlist', fileName); } catch (e) { debugPrint(e.toString()); state = Playlist.error(); @@ -37,10 +39,12 @@ class PlaylistNotifier extends StateNotifier { songsDir = await songsDirectory(); // get already selected playlist from hive database - String? playlistFileName = Hive.box('settings').get('playlist'); + //String? playlistFileName = Hive.box('settings').get('playlist'); + + String? playlistFileName = ref.read(settingsProvider)['playlist']; if (playlistFileName != null) { - await select( playlistFileName); + await select(playlistFileName); } await _listen(); diff --git a/lib/notifiers/settings_notifier.dart b/lib/notifiers/settings_notifier.dart new file mode 100644 index 0000000..4ad1ebf --- /dev/null +++ b/lib/notifiers/settings_notifier.dart @@ -0,0 +1,34 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../core/file_utils.dart'; + +class SettingsNotifier extends StateNotifier> { + SettingsNotifier(this.fileName) : super({}) { + _init(); + } + + String fileName; + + void _init() { + try { + state = Map.from(jsonDecode(File(fileName).readAsStringSync())); + } catch (e) { + // + } + } + + void update(String key, String value) { + state.update( + key, + (_) => value, + ifAbsent: () => value, + ); + + state = Map.from(state); + + FileUtils.saveSettings(state); + } +} diff --git a/lib/notifiers/slides_notifier.dart b/lib/notifiers/slides_notifier.dart index aa0c3c9..34fbe55 100644 --- a/lib/notifiers/slides_notifier.dart +++ b/lib/notifiers/slides_notifier.dart @@ -53,7 +53,13 @@ class SlidesNotifier extends StateNotifier> { _scriptureReference = scripture.scriptureRef; _setSlidesPanelTitle(scriptureRefToRefString(scripture.scriptureRef)); + bool breakOnNewVerse = _ref.read(settingsProvider.select((value) => value['break_on_new_verse'])) == 'true' ? true : false; + if (endVerse != null) { + if (breakOnNewVerse == false) { + stitchVerses(scripture, startVerse, endVerse); + return; + } _setMultipleScriptureSlides(scripture, startVerse, endVerse); return; } @@ -196,4 +202,42 @@ class SlidesNotifier extends StateNotifier> { void _setSlidesPanelTitle(String title) { _ref.read(slidePanelTitleProvider.notifier).state = title; } + + void stitchVerses(Scripture scripture, int startVerse, int endVerse) { + String passage = ''; + + scripture.verses! + .getRange( + startVerse - 1, + endVerse, + ) + .forEach( + (verse) { + String text = _getTextWithSuperscript(verse.num, verse.text); + + passage = '$passage $text'; + }, + ); + + int maxChars = maxCharacters( + passage, + const TextStyle( + fontFamily: 'LemonMilk', + fontSize: 80, + color: Colors.white, + ), + ); + + if (passage.length > maxChars) { + state = splitSlides(passage, maxChars, scripture); + } else { + state = [ + Slide( + text: passage, + reference: _scriptureRefToString(scripture.scriptureRef), + slideType: SlideType.scripture, + ), + ]; + } + } } diff --git a/lib/panels/scripture_settings.dart b/lib/panels/scripture_settings.dart index 64f6c4c..c9a4c11 100644 --- a/lib/panels/scripture_settings.dart +++ b/lib/panels/scripture_settings.dart @@ -34,6 +34,16 @@ class ScriptureSettings extends ConsumerWidget { }, child: const Text('Save Verses To Playlist'), ), + const SizedBox( + height: 50, + ), + SwitchListTile( + title: const Text('Break on new verse'), + value: ref.watch(settingsProvider.select((value) => value['break_on_new_verse'])) == 'true' ? true : false, + onChanged: (bool newValue) { + ref.read(settingsProvider.notifier).update('break_on_new_verse', newValue.toString()); + }, + ), ], ), ); diff --git a/lib/windows/main_window.dart b/lib/windows/main_window.dart index cbc253f..66c4245 100644 --- a/lib/windows/main_window.dart +++ b/lib/windows/main_window.dart @@ -21,108 +21,120 @@ class MainWindow extends ConsumerWidget { // TODO: change appbar to custom app bar @override Widget build(BuildContext context, WidgetRef ref) { - return Scaffold( - appBar: AppBar( - title: const Text('FreeCPS'), - actions: [ - TextButton( - onPressed: () { - Navigator.push( - context, - CustomPopupRoute( - builder: (context) { - return const MediaCenter(); - }, - ), - ); - }, - child: const Text('Media'), - ), - const VerticalDivider( - width: 30, - ), - const Center( - child: Text('LIVE'), - ), - TapDebouncer( - cooldown: const Duration(seconds: 3), - onTap: () async { - bool isLive = ref.watch(liveProvider); + return ref.watch(initProvider).when( + data: (data) { + return Scaffold( + appBar: AppBar( + title: const Text('FreeCPS'), + actions: [ + TextButton( + onPressed: () { + Navigator.push( + context, + CustomPopupRoute( + builder: (context) { + return const MediaCenter(); + }, + ), + ); + }, + child: const Text('Media'), + ), + const VerticalDivider( + width: 30, + ), + const Center( + child: Text('LIVE'), + ), + TapDebouncer( + cooldown: const Duration(seconds: 3), + onTap: () async { + bool isLive = ref.watch(liveProvider); - if (!isLive) { - ProjectionUtils.open(); - } else { - await ProjectionUtils.close(); - } + if (!isLive) { + ProjectionUtils.open(); + } else { + await ProjectionUtils.close(); + } - ref.read(liveProvider.notifier).state = !isLive; - }, - builder: (context, onTap) { - return Switch( - focusNode: FocusNode(canRequestFocus: false), - value: ref.watch(liveProvider), - onChanged: (value) { - if (onTap == null) return; - onTap(); + ref.read(liveProvider.notifier).state = !isLive; }, - ); - }, + builder: (context, onTap) { + return Switch( + focusNode: FocusNode(canRequestFocus: false), + value: ref.watch(liveProvider), + onChanged: (value) { + if (onTap == null) return; + onTap(); + }, + ); + }, + ), + ], ), - ], - ), - body: RawKeyboardListener( - focusNode: FocusNode(), - autofocus: true, - onKey: (event) { - if (event is RawKeyDownEvent && event.isKeyPressed(LogicalKeyboardKey.enter) || event.isKeyPressed(LogicalKeyboardKey.numpadEnter)) { - ref.read(projectionSlidesProvider.notifier).generateScriptureSlides(scripture: ref.read(scriptureProvider)); - } else if (event is RawKeyDownEvent && event.isKeyPressed(LogicalKeyboardKey.controlLeft) || - event.isKeyPressed(LogicalKeyboardKey.shiftLeft)) { - ref.read(ctrlKeyNotifier.notifier).state = true; - return; - } else { - ref.read(ctrlKeyNotifier.notifier).state = false; - } - }, - child: ResizableWidget( - isHorizontalSeparator: true, - isDisabledSmartHide: true, - percentages: const [0.65, 0.35], // optional - minPercentages: const [0.6, 0.0], - maxPercentages: const [0.7, double.infinity], - children: [ - ResizableWidget( + body: RawKeyboardListener( + focusNode: FocusNode(), + autofocus: true, + onKey: (event) { + if (event is RawKeyDownEvent && event.isKeyPressed(LogicalKeyboardKey.enter) || event.isKeyPressed(LogicalKeyboardKey.numpadEnter)) { + ref.read(projectionSlidesProvider.notifier).generateScriptureSlides(scripture: ref.read(scriptureProvider)); + } else if (event is RawKeyDownEvent && event.isKeyPressed(LogicalKeyboardKey.controlLeft) || + event.isKeyPressed(LogicalKeyboardKey.shiftLeft)) { + ref.read(ctrlKeyNotifier.notifier).state = true; + return; + } else { + ref.read(ctrlKeyNotifier.notifier).state = false; + } + }, + child: ResizableWidget( + isHorizontalSeparator: true, isDisabledSmartHide: true, - percentages: const [0.175, 0.65, 0.175], // optional - minPercentages: const [0.15, 0.0, 0.15], - maxPercentages: const [0.2, double.infinity, 0.2], - children: const [ - PlaylistPanel(), - SlidesPanel(), - ProjectionControls(), - ], - ), - Row( - children: const [ - Expanded( - flex: 1, - child: ScripturePickerPanel(), + percentages: const [0.65, 0.35], // optional + minPercentages: const [0.6, 0.0], + maxPercentages: const [0.7, double.infinity], + children: [ + ResizableWidget( + isDisabledSmartHide: true, + percentages: const [0.175, 0.65, 0.175], // optional + minPercentages: const [0.15, 0.0, 0.15], + maxPercentages: const [0.2, double.infinity, 0.2], + children: const [ + PlaylistPanel(), + SlidesPanel(), + ProjectionControls(), + ], ), - VerticalDivider(), - Expanded( - flex: 4, - child: VersesList(), - ), - VerticalDivider(), - Expanded( - flex: 1, - child: ScriptureSettings(), + Row( + children: const [ + Expanded( + flex: 1, + child: ScripturePickerPanel(), + ), + VerticalDivider(), + Expanded( + flex: 4, + child: VersesList(), + ), + VerticalDivider(), + Expanded( + flex: 1, + child: ScriptureSettings(), + ), + ], ), ], ), - ], - ), - ), + ), + ); + }, + error: (e, s) { + return Center(child: Text(e.toString())); + }, + loading: () { + return const Center( + child: CircularProgressIndicator(), + ); + }, ); } } diff --git a/pubspec.lock b/pubspec.lock index 70a5f5d..7d61b1a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -400,14 +400,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" - hive: - dependency: "direct main" - description: - name: hive - sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" - url: "https://pub.dev" - source: hosted - version: "2.2.3" http: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b2fb45d..f5b6fe1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -52,7 +52,6 @@ dependencies: mime: ^1.0.3 google_fonts: ^3.0.1 nanoid: ^1.0.0 - hive: ^2.2.3 resizable_widget: git: