From 5d36aede4ac7be0b4d7dcc90a7088c371b232dfa Mon Sep 17 00:00:00 2001 From: Jagandeep Brar Date: Sun, 14 Jun 2020 23:58:49 -0500 Subject: [PATCH] [Release] v2.3.0 (230004) (#220) * [Packages] Updated packages * [Sonarr/Series] Switched to bottom navigation, removed sliver app bar * [Sonarr/Series] Removed bottom padding from overview * [README] Added AltStore, Removed Feedback, Updated Android APK Build (#213) * [Snackbar] Fixed cases where iOS swipe-back would lock UI * [Android/Back Button] Back button now shows drawer on primary routes instead of closing app * [Wake on LAN] Added configuration to hive * [Settings/Modules] Added Wake on LAN section * [Wake on LAN] Added entry to drawer * [Lidarr/Details] Removed sliver app bar, removed fanart, switched to bottom navigation bar * [Radarr/Details] Removed sliver app bar, removed fanart, switched to bottom navigation bar * [Wake on LAN] Show toast on action * [Wake on LAN] Created RawDatagramSocket binding * [WoL] Full support for Wake on LAN functionality * [Semantics] Cleanup * [TestFlight] v2.3.0 (230002) * [Settings/Modules] Implemented settings UI for custom headers * [Custom Headers] Finished UI implementation * [Custom Headers] Integrated into API calls * [TestFlight] v2.3.0 (230003) * [Images] Attach custom headers to all network image requests * [Everything] Many Changes (Read Notes) (#218) - Unified the structure of each module (core, routes, widgets at the root of every module) - Cleaned Most Remaining Dialogs (SABnzbd and NZBGet remain) - Reduced font-size across dialogs - Unified padding across all dialogs (that have been cleaned so far) - Changelog is now a routed page, not a dialog prompt - SABnzbd: Fixed bug where setting priority to "stop" would cause a fatal crash * [Networking] Added RegExp Validation for IPv4 & MAC Addresses * [Networking] Improved network address classes * Finished Dialog Rework * [TestFlight] v2.3.0 (230004) * [README] Updated README for v2.3.0 --- .gitignore | 1 + README.md | 6 +- ios/Podfile.lock | 60 +- ios/Runner.xcodeproj/project.pbxproj | 3 - lib/core.dart | 2 + lib/core/configuration/import.dart | 9 + lib/core/constants.dart | 44 +- lib/core/database/adapters/profile.dart | 67 + lib/core/database/adapters/profile.g.dart | 28 +- lib/core/dialogs.dart | 96 + lib/core/module_flags.dart | 5 +- lib/core/networking.dart | 2 + lib/core/networking/ipv4_address.dart | 18 + lib/core/networking/mac_address.dart | 24 + lib/core/routes.dart | 8 + lib/core/ui/card/card_background.dart | 3 +- lib/core/ui/description_block.dart | 5 +- lib/core/ui/dialogs.dart | 155 +- lib/core/ui/dialogs/lidarr.dart | 607 ----- lib/core/ui/dialogs/nzbget.dart | 1574 ------------- lib/core/ui/dialogs/radarr.dart | 661 ------ lib/core/ui/dialogs/sabnzbd.dart | 2013 ----------------- lib/core/ui/dialogs/settings.dart | 561 ----- lib/core/ui/dialogs/sonarr.dart | 831 ------- lib/core/ui/dialogs/system.dart | 435 ---- lib/core/ui/navigation.dart | 2 - lib/core/ui/navigation/appbar.dart | 5 +- lib/core/ui/navigation/drawer.dart | 43 +- lib/core/ui/navigation/sliver_appbar.dart | 70 - .../ui/navigation/sliver_appbar_tabs.dart | 73 - lib/core/ui/rive.dart | 1 - lib/core/ui/snackbar.dart | 94 +- lib/modules.dart | 1 + lib/modules/home/core/api/api.dart | 54 +- lib/modules/home/routes/home.dart | 44 +- lib/modules/home/routes/quick_access.dart | 18 +- lib/modules/home/widgets/calendar.dart | 38 +- lib/modules/lidarr/core.dart | 1 + lib/modules/lidarr/core/api/api.dart | 4 +- lib/modules/lidarr/core/constants.dart | 4 +- lib/modules/lidarr/core/dialogs.dart | 242 ++ lib/modules/lidarr/core/state.dart | 8 + lib/modules/lidarr/routes/add_details.dart | 7 +- lib/modules/lidarr/routes/details_artist.dart | 58 +- lib/modules/lidarr/routes/edit_artist.dart | 6 +- lib/modules/lidarr/routes/lidarr.dart | 38 +- lib/modules/lidarr/routes/search_details.dart | 56 +- lib/modules/lidarr/widgets.dart | 1 + .../lidarr/widgets/artist_navigation_bar.dart | 46 + .../lidarr/widgets/catalogue_tile.dart | 7 +- .../lidarr/widgets/details_album_list.dart | 2 +- .../lidarr/widgets/details_album_tile.dart | 1 + .../lidarr/widgets/details_edit_button.dart | 6 +- .../lidarr/widgets/details_overview.dart | 3 +- lib/modules/lidarr/widgets/missing_tile.dart | 5 +- .../lidarr/widgets/search_result_tile.dart | 4 +- lib/modules/nzbget.dart | 3 +- lib/modules/nzbget/core.dart | 5 + lib/modules/nzbget/{ => core}/api.dart | 0 lib/modules/nzbget/{ => core}/api/api.dart | 4 +- lib/modules/nzbget/{ => core}/api/data.dart | 0 .../nzbget/{ => core}/api/data/category.dart | 0 .../nzbget/{ => core}/api/data/history.dart | 0 .../nzbget/{ => core}/api/data/log.dart | 0 .../nzbget/{ => core}/api/data/priority.dart | 0 .../nzbget/{ => core}/api/data/queue.dart | 0 .../nzbget/{ => core}/api/data/sort.dart | 0 .../{ => core}/api/data/statistics.dart | 0 .../nzbget/{ => core}/api/data/status.dart | 0 lib/modules/nzbget/core/constants.dart | 14 + lib/modules/nzbget/core/database.dart | 3 + lib/modules/nzbget/core/dialogs.dart | 568 +++++ lib/modules/nzbget/{ => core}/state.dart | 0 .../nzbget/routes/history_details.dart | 4 +- lib/modules/nzbget/routes/nzbget.dart | 42 +- lib/modules/nzbget/widgets/app_bar_stats.dart | 4 +- lib/modules/nzbget/widgets/history_tile.dart | 2 +- lib/modules/nzbget/widgets/log_tile.dart | 2 +- lib/modules/nzbget/widgets/queue_fab.dart | 4 +- lib/modules/nzbget/widgets/queue_tile.dart | 12 +- lib/modules/radarr/core.dart | 1 + lib/modules/radarr/core/api/api.dart | 4 +- lib/modules/radarr/core/constants.dart | 4 +- lib/modules/radarr/core/dialogs.dart | 269 +++ lib/modules/radarr/core/state.dart | 8 + lib/modules/radarr/routes/add_details.dart | 7 +- lib/modules/radarr/routes/details_movie.dart | 60 +- .../radarr/routes/details_search_details.dart | 56 +- lib/modules/radarr/routes/edit_movie.dart | 6 +- lib/modules/radarr/routes/radarr.dart | 38 +- lib/modules/radarr/widgets.dart | 1 + .../radarr/widgets/catalogue_tile.dart | 7 +- .../radarr/widgets/details_edit_button.dart | 6 +- lib/modules/radarr/widgets/details_files.dart | 5 +- .../radarr/widgets/details_overview.dart | 3 +- .../radarr/widgets/details_search_tile.dart | 4 +- lib/modules/radarr/widgets/missing_tile.dart | 5 +- .../radarr/widgets/movie_navigation_bar.dart | 48 + lib/modules/radarr/widgets/upcoming_tile.dart | 5 +- lib/modules/sabnzbd.dart | 3 +- lib/modules/sabnzbd/core.dart | 5 + lib/modules/sabnzbd/{ => core}/api.dart | 0 lib/modules/sabnzbd/{ => core}/api/api.dart | 12 +- lib/modules/sabnzbd/{ => core}/api/data.dart | 0 .../sabnzbd/{ => core}/api/data/category.dart | 0 .../sabnzbd/{ => core}/api/data/history.dart | 0 .../sabnzbd/{ => core}/api/data/queue.dart | 0 .../{ => core}/api/data/statistics.dart | 0 .../api/data/statistics_server.dart | 0 .../sabnzbd/{ => core}/api/data/status.dart | 0 lib/modules/sabnzbd/core/constants.dart | 14 + lib/modules/sabnzbd/core/database.dart | 3 + lib/modules/sabnzbd/core/dialogs.dart | 656 ++++++ lib/modules/sabnzbd/{ => core}/state.dart | 0 .../sabnzbd/routes/history_details.dart | 6 +- lib/modules/sabnzbd/routes/sabnzbd.dart | 46 +- .../sabnzbd/widgets/app_bar_stats.dart | 4 +- lib/modules/sabnzbd/widgets/history_tile.dart | 4 +- lib/modules/sabnzbd/widgets/queue_fab.dart | 4 +- lib/modules/sabnzbd/widgets/queue_tile.dart | 12 +- lib/modules/search.dart | 3 +- lib/modules/search/core.dart | 5 + lib/modules/search/{ => core}/api.dart | 0 lib/modules/search/{ => core}/api/api.dart | 4 +- lib/modules/search/{ => core}/api/data.dart | 0 .../search/{ => core}/api/data/category.dart | 2 +- .../search/{ => core}/api/data/result.dart | 0 .../{ => core}/api/data/subcategory.dart | 0 lib/modules/search/core/constants.dart | 14 + lib/modules/search/core/database.dart | 3 + .../search/core/dialogs.dart} | 59 +- lib/modules/search/{ => core}/state.dart | 2 +- lib/modules/search/routes/details.dart | 2 +- lib/modules/search/routes/indexers.dart | 30 +- .../search/widgets/download_button.dart | 2 +- lib/modules/settings.dart | 3 +- lib/modules/settings/core.dart | 5 + lib/modules/settings/{ => core}/api.dart | 0 lib/modules/settings/{ => core}/api/api.dart | 0 lib/modules/settings/core/constants.dart | 14 + lib/modules/settings/core/database.dart | 3 + lib/modules/settings/core/dialogs.dart | 850 +++++++ lib/modules/settings/{ => core}/state.dart | 0 lib/modules/settings/routes.dart | 3 + .../settings/routes/logs/logs_details.dart | 6 +- lib/modules/settings/routes/modules.dart | 22 +- .../routes/modules/automation_lidarr.dart | 119 +- .../automation_lidarr/automation_lidarr.dart | 132 ++ .../automation_lidarr_headers.dart | 110 + .../routes/modules/automation_radarr.dart | 119 +- .../automation_radarr/automation_radarr.dart | 132 ++ .../automation_radarr_headers.dart | 110 + .../routes/modules/automation_sonarr.dart | 138 +- .../automation_sonarr/automation_sonarr.dart | 143 ++ .../automation_sonarr_headers.dart | 110 + .../routes/modules/clients_nzbget.dart | 157 +- .../clients_nzbget/clients_nzbget.dart | 170 ++ .../clients_nzbget_headers.dart | 110 + .../routes/modules/clients_sabnzbd.dart | 119 +- .../clients_sabnzbd/clients_sabnzbd.dart | 132 ++ .../clients_sabnzbd_headers.dart | 110 + .../routes/modules/general_search_add.dart | 6 +- .../routes/modules/general_search_edit.dart | 10 +- .../routes/modules/general_wake_on_lan.dart | 89 + lib/modules/settings/routes/settings.dart | 2 +- lib/modules/settings/routes/system.dart | 11 +- .../settings/routes/system/changelog.dart | 71 + .../routes/system/changelog_details.dart | 118 + .../widgets/general/configuration/backup.dart | 3 +- .../general/configuration/restore.dart | 3 +- .../widgets/general/logs/clear_tile.dart | 3 +- .../widgets/general/logs/export_tile.dart | 3 +- .../widgets/general/profile/add_tile.dart | 3 +- .../widgets/general/profile/delete_tile.dart | 3 +- .../widgets/general/profile/enabled_tile.dart | 3 +- .../widgets/general/profile/rename_tile.dart | 5 +- .../modules/calendar/start_date_tile.dart | 3 +- .../modules/calendar/start_size_tile.dart | 3 +- .../widgets/modules/lunasea/browser_tile.dart | 3 +- .../widgets/system/reset_lunasea_tile.dart | 3 +- lib/modules/sonarr/core.dart | 1 + lib/modules/sonarr/core/api/api.dart | 4 +- lib/modules/sonarr/core/constants.dart | 4 +- lib/modules/sonarr/core/dialogs.dart | 333 +++ lib/modules/sonarr/core/state.dart | 8 + lib/modules/sonarr/routes/add_details.dart | 7 +- lib/modules/sonarr/routes/details_season.dart | 4 +- lib/modules/sonarr/routes/details_series.dart | 56 +- lib/modules/sonarr/routes/edit_series.dart | 6 +- lib/modules/sonarr/routes/search_details.dart | 56 +- lib/modules/sonarr/routes/sonarr.dart | 38 +- lib/modules/sonarr/widgets.dart | 1 + .../sonarr/widgets/catalogue_tile.dart | 7 +- .../sonarr/widgets/details_edit_button.dart | 6 +- .../sonarr/widgets/details_overview.dart | 4 +- .../sonarr/widgets/details_season_list.dart | 1 - .../widgets/details_season_list_tile.dart | 2 +- lib/modules/sonarr/widgets/episode_tile.dart | 4 +- lib/modules/sonarr/widgets/missing_tile.dart | 5 +- .../sonarr/widgets/search_result_tile.dart | 4 +- .../sonarr/widgets/series_navigation_bar.dart | 46 + lib/modules/sonarr/widgets/upcoming_tile.dart | 5 +- lib/modules/wake_on_lan.dart | 1 + lib/modules/wake_on_lan/core.dart | 2 + lib/modules/wake_on_lan/core/api.dart | 44 + lib/modules/wake_on_lan/core/constants.dart | 14 + pubspec.lock | 55 +- pubspec.yaml | 26 +- scripts/build_release | 1 - scripts/update_packages | 4 - 210 files changed, 6010 insertions(+), 8160 deletions(-) create mode 100644 lib/core/dialogs.dart create mode 100644 lib/core/networking.dart create mode 100644 lib/core/networking/ipv4_address.dart create mode 100644 lib/core/networking/mac_address.dart delete mode 100644 lib/core/ui/dialogs/lidarr.dart delete mode 100644 lib/core/ui/dialogs/nzbget.dart delete mode 100644 lib/core/ui/dialogs/radarr.dart delete mode 100644 lib/core/ui/dialogs/sabnzbd.dart delete mode 100644 lib/core/ui/dialogs/settings.dart delete mode 100644 lib/core/ui/dialogs/sonarr.dart delete mode 100644 lib/core/ui/dialogs/system.dart delete mode 100644 lib/core/ui/navigation/sliver_appbar.dart delete mode 100644 lib/core/ui/navigation/sliver_appbar_tabs.dart create mode 100644 lib/modules/lidarr/core/dialogs.dart create mode 100644 lib/modules/lidarr/widgets/artist_navigation_bar.dart create mode 100644 lib/modules/nzbget/core.dart rename lib/modules/nzbget/{ => core}/api.dart (100%) rename lib/modules/nzbget/{ => core}/api/api.dart (99%) rename lib/modules/nzbget/{ => core}/api/data.dart (100%) rename lib/modules/nzbget/{ => core}/api/data/category.dart (100%) rename lib/modules/nzbget/{ => core}/api/data/history.dart (100%) rename lib/modules/nzbget/{ => core}/api/data/log.dart (100%) rename lib/modules/nzbget/{ => core}/api/data/priority.dart (100%) rename lib/modules/nzbget/{ => core}/api/data/queue.dart (100%) rename lib/modules/nzbget/{ => core}/api/data/sort.dart (100%) rename lib/modules/nzbget/{ => core}/api/data/statistics.dart (100%) rename lib/modules/nzbget/{ => core}/api/data/status.dart (100%) create mode 100644 lib/modules/nzbget/core/constants.dart create mode 100644 lib/modules/nzbget/core/database.dart create mode 100644 lib/modules/nzbget/core/dialogs.dart rename lib/modules/nzbget/{ => core}/state.dart (100%) create mode 100644 lib/modules/radarr/core/dialogs.dart create mode 100644 lib/modules/radarr/widgets/movie_navigation_bar.dart create mode 100644 lib/modules/sabnzbd/core.dart rename lib/modules/sabnzbd/{ => core}/api.dart (100%) rename lib/modules/sabnzbd/{ => core}/api/api.dart (98%) rename lib/modules/sabnzbd/{ => core}/api/data.dart (100%) rename lib/modules/sabnzbd/{ => core}/api/data/category.dart (100%) rename lib/modules/sabnzbd/{ => core}/api/data/history.dart (100%) rename lib/modules/sabnzbd/{ => core}/api/data/queue.dart (100%) rename lib/modules/sabnzbd/{ => core}/api/data/statistics.dart (100%) rename lib/modules/sabnzbd/{ => core}/api/data/statistics_server.dart (100%) rename lib/modules/sabnzbd/{ => core}/api/data/status.dart (100%) create mode 100644 lib/modules/sabnzbd/core/constants.dart create mode 100644 lib/modules/sabnzbd/core/database.dart create mode 100644 lib/modules/sabnzbd/core/dialogs.dart rename lib/modules/sabnzbd/{ => core}/state.dart (100%) create mode 100644 lib/modules/search/core.dart rename lib/modules/search/{ => core}/api.dart (100%) rename lib/modules/search/{ => core}/api/api.dart (97%) rename lib/modules/search/{ => core}/api/data.dart (100%) rename lib/modules/search/{ => core}/api/data/category.dart (96%) rename lib/modules/search/{ => core}/api/data/result.dart (100%) rename lib/modules/search/{ => core}/api/data/subcategory.dart (100%) create mode 100644 lib/modules/search/core/constants.dart create mode 100644 lib/modules/search/core/database.dart rename lib/{core/ui/dialogs/search.dart => modules/search/core/dialogs.dart} (64%) rename lib/modules/search/{ => core}/state.dart (96%) create mode 100644 lib/modules/settings/core.dart rename lib/modules/settings/{ => core}/api.dart (100%) rename lib/modules/settings/{ => core}/api/api.dart (100%) create mode 100644 lib/modules/settings/core/constants.dart create mode 100644 lib/modules/settings/core/database.dart create mode 100644 lib/modules/settings/core/dialogs.dart rename lib/modules/settings/{ => core}/state.dart (100%) create mode 100644 lib/modules/settings/routes/modules/automation_lidarr/automation_lidarr.dart create mode 100644 lib/modules/settings/routes/modules/automation_lidarr/automation_lidarr_headers.dart create mode 100644 lib/modules/settings/routes/modules/automation_radarr/automation_radarr.dart create mode 100644 lib/modules/settings/routes/modules/automation_radarr/automation_radarr_headers.dart create mode 100644 lib/modules/settings/routes/modules/automation_sonarr/automation_sonarr.dart create mode 100644 lib/modules/settings/routes/modules/automation_sonarr/automation_sonarr_headers.dart create mode 100644 lib/modules/settings/routes/modules/clients_nzbget/clients_nzbget.dart create mode 100644 lib/modules/settings/routes/modules/clients_nzbget/clients_nzbget_headers.dart create mode 100644 lib/modules/settings/routes/modules/clients_sabnzbd/clients_sabnzbd.dart create mode 100644 lib/modules/settings/routes/modules/clients_sabnzbd/clients_sabnzbd_headers.dart create mode 100644 lib/modules/settings/routes/modules/general_wake_on_lan.dart create mode 100644 lib/modules/settings/routes/system/changelog.dart create mode 100644 lib/modules/settings/routes/system/changelog_details.dart create mode 100644 lib/modules/sonarr/core/dialogs.dart create mode 100644 lib/modules/sonarr/widgets/series_navigation_bar.dart create mode 100644 lib/modules/wake_on_lan.dart create mode 100644 lib/modules/wake_on_lan/core.dart create mode 100644 lib/modules/wake_on_lan/core/api.dart create mode 100644 lib/modules/wake_on_lan/core/constants.dart delete mode 100755 scripts/update_packages diff --git a/.gitignore b/.gitignore index 60c5a878..af5960df 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ lib/generated_plugin_registrant.dart # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages +ios/Flutter/.last_build_id # LunaSea LunaSea.ipa diff --git a/README.md b/README.md index 7615fa5c..8bfd289c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ [![Subreddit](https://img.shields.io/reddit/subreddit-subscribers/LunaSeaApp?label=Subreddit&logo=reddit&logoColor=white&style=for-the-badge)](https://www.reddit.com/r/LunaSeaApp) -[![AltStore](https://img.shields.io/badge/AltStore-v2.2.0-red?style=for-the-badge&logo=palo-alto-software&color=%233B878E&logoColor=white)](https://altstore.lunasea.app) -[![App Store](https://img.shields.io/badge/App%20Store-v2.2.0-red?style=for-the-badge&logo=app-store&color=%230D96F6&logoColor=white)](https://apps.apple.com/us/app/lunasea/id1496797802?ls=1) -[![Play Store](https://img.shields.io/badge/Google%20Play-v2.2.0-red?style=for-the-badge&logo=google-play&color=%230F9D58&logoColor=white)](https://play.google.com/store/apps/details?id=app.lunasea.lunasea) +[![App Store](https://img.shields.io/badge/App%20Store-v2.3.0-red?style=for-the-badge&logo=app-store&color=%230D96F6&logoColor=white)](https://apps.apple.com/us/app/lunasea/id1496797802?ls=1) +[![Play Store](https://img.shields.io/badge/Google%20Play-v2.3.0-red?style=for-the-badge&logo=google-play&color=%230F9D58&logoColor=white)](https://play.google.com/store/apps/details?id=app.lunasea.lunasea) ![LunaSea](https://www.lunasea.app/images/banner.png) @@ -16,6 +15,7 @@ LunaSea is a fully featured, open source self-hosted media manager! Focused on g - NZBGet - NZBHydra2 - Newznab Indexer Searching +- Wake on LAN LunaSea even comes with support for multiple instances of applications using profiles, backing up and restoring your configuration to your filesystem, an AMOLED black theme, and more! diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 790f7bc9..797e8eac 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,6 +1,39 @@ PODS: + - DKImagePickerController/Core (4.3.1): + - DKImagePickerController/ImageDataManager + - DKImagePickerController/Resource + - DKImagePickerController/ImageDataManager (4.3.1) + - DKImagePickerController/PhotoGallery (4.3.1): + - DKImagePickerController/Core + - DKPhotoGallery + - DKImagePickerController/Resource (4.3.1) + - DKPhotoGallery (0.0.15): + - DKPhotoGallery/Core (= 0.0.15) + - DKPhotoGallery/Model (= 0.0.15) + - DKPhotoGallery/Preview (= 0.0.15) + - DKPhotoGallery/Resource (= 0.0.15) + - SDWebImage + - SDWebImageFLPlugin + - DKPhotoGallery/Core (0.0.15): + - DKPhotoGallery/Model + - DKPhotoGallery/Preview + - SDWebImage + - SDWebImageFLPlugin + - DKPhotoGallery/Model (0.0.15): + - SDWebImage + - SDWebImageFLPlugin + - DKPhotoGallery/Preview (0.0.15): + - DKPhotoGallery/Model + - DKPhotoGallery/Resource + - SDWebImage + - SDWebImageFLPlugin + - DKPhotoGallery/Resource (0.0.15): + - SDWebImage + - SDWebImageFLPlugin - file_picker (0.0.1): + - DKImagePickerController/PhotoGallery - Flutter + - FLAnimatedImage (1.0.12) - Flutter (1.0.0) - flutter_plugin_android_lifecycle (0.0.1): - Flutter @@ -14,6 +47,12 @@ PODS: - Flutter - "permission_handler (4.4.0+hotfix.2)": - Flutter + - SDWebImage (5.8.0): + - SDWebImage/Core (= 5.8.0) + - SDWebImage/Core (5.8.0) + - SDWebImageFLPlugin (0.4.0): + - FLAnimatedImage (>= 1.0.11) + - SDWebImage/Core (~> 5.6) - url_launcher (0.0.1): - Flutter - url_launcher_macos (0.0.1): @@ -34,6 +73,14 @@ DEPENDENCIES: - url_launcher_macos (from `.symlinks/plugins/url_launcher_macos/ios`) - url_launcher_web (from `.symlinks/plugins/url_launcher_web/ios`) +SPEC REPOS: + trunk: + - DKImagePickerController + - DKPhotoGallery + - FLAnimatedImage + - SDWebImage + - SDWebImageFLPlugin + EXTERNAL SOURCES: file_picker: :path: ".symlinks/plugins/file_picker/ios" @@ -59,15 +106,20 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/url_launcher_web/ios" SPEC CHECKSUMS: - file_picker: 408623be2125b79a4539cf703be3d4b3abe5e245 + DKImagePickerController: f3d397887f38b98276a506b02ed9c167425f7cf3 + DKPhotoGallery: e880aef16c108333240e1e7327896f2ea380f4f0 + file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 + FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31 Flutter: 0e3d915762c693b495b44d77113d4970485de6ec flutter_plugin_android_lifecycle: 47de533a02850f070f5696a623995e93eddcdb9b in_app_purchase: 3e2155afa9d03d4fa32d9e62d567885080ce97d6 - package_info: 48b108e75b8802c2d5e126f208ef540561c98aef - path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d + package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 + path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 permission_handler: d5d46b2ca93671e8dcf870b949e304c7ddb65ad0 - url_launcher: a1c0cc845906122c4784c542523d8cacbded5626 + SDWebImage: 84000f962cbfa70c07f19d2234cbfcf5d779b5dc + SDWebImageFLPlugin: 6c2295fb1242d44467c6c87dc5db6b0a13228fd8 + url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313 url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 5c304317..de6122d4 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -314,7 +314,6 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -396,7 +395,6 @@ }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -452,7 +450,6 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; diff --git a/lib/core.dart b/lib/core.dart index d3985ef7..62f813a7 100644 --- a/lib/core.dart +++ b/lib/core.dart @@ -2,10 +2,12 @@ export './core/constants.dart'; export './core/configuration.dart'; export './core/data.dart'; export './core/database.dart'; +export './core/dialogs.dart'; export './core/extensions.dart'; export './core/in_app_purchases.dart'; export './core/logger.dart'; export './core/module_flags.dart'; +export './core/networking.dart'; export './core/providers.dart'; export './core/routes.dart'; export './core/theme.dart'; diff --git a/lib/core/configuration/import.dart b/lib/core/configuration/import.dart index 50f24a15..13608e6a 100644 --- a/lib/core/configuration/import.dart +++ b/lib/core/configuration/import.dart @@ -19,21 +19,25 @@ class Import { sonarrKey: profile['sonarrKey'] ?? '', sonarrStrictTLS: profile['sonarrStrictTLS'] ?? true, sonarrVersion3: profile['sonarrVersion3'] ?? false, + sonarrHeaders: profile['sonarrHeaders'] ?? {}, //Radarr radarrEnabled: profile['radarrEnabled'] ?? false, radarrHost: profile['radarrHost'] ?? '', radarrKey: profile['radarrKey'] ?? '', radarrStrictTLS: profile['radarrStrictTLS'] ?? true, + radarrHeaders: profile['radarrHeaders'] ?? {}, //Lidarr lidarrEnabled: profile['lidarrEnabled'] ?? false, lidarrHost: profile['lidarrHost'] ?? '', lidarrKey: profile['lidarrKey'] ?? '', lidarrStrictTLS: profile['lidarrStrictTLS'] ?? true, + lidarrHeaders: profile['lidarrHeaders'] ?? {}, //SABnzbd sabnzbdEnabled: profile['sabnzbdEnabled'] ?? false, sabnzbdHost: profile['sabnzbdHost'] ?? '', sabnzbdKey: profile['sabnzbdKey'] ?? '', sabnzbdStrictTLS: profile['sabnzbdStrictTLS'] ?? true, + sabnzbdHeaders: profile['sabnzbdHeaders'] ?? {}, //NZBGet nzbgetEnabled: profile['nzbgetEnabled'] ?? false, nzbgetHost: profile['nzbgetHost'] ?? '', @@ -41,6 +45,11 @@ class Import { nzbgetPass: profile['nzbgetPass'] ?? '', nzbgetStrictTLS: profile['nzbgetStrictTLS'] ?? true, nzbgetBasicAuth: profile['nzbgetBasicAuth'] ?? false, + nzbgetHeaders: profile['nzbgetHeaders'] ?? {}, + //Wake on LAN + wakeOnLANEnabled: profile['wakeOnLANEnabled'] ?? false, + wakeOnLANBroadcastAddress: profile['wakeOnLANBroadcastAddress'] ?? '', + wakeOnLANMACAddress: profile['wakeOnLANMACAddress'] ?? '', )); } } diff --git a/lib/core/constants.dart b/lib/core/constants.dart index 87e5e13e..e993e071 100644 --- a/lib/core/constants.dart +++ b/lib/core/constants.dart @@ -1,38 +1,18 @@ import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; import 'package:lunasea/modules.dart'; class Constants { Constants._(); //Services - static const Map SERVICE_MAP = { - 'lidarr': LidarrConstants.SERVICE_MAP, - 'radarr': RadarrConstants.SERVICE_MAP, - 'sonarr': SonarrConstants.SERVICE_MAP, - 'nzbget': { - 'name': 'NZBGet', - 'desc': 'Manage Usenet Downloads', - 'icon': CustomIcons.nzbget, - 'route': '/nzbget', - }, - 'sabnzbd': { - 'name': 'SABnzbd', - 'desc': 'Manage Usenet Downloads', - 'icon': CustomIcons.sabnzbd, - 'route': '/sabnzbd', - }, - 'search': { - 'name': 'Search', - 'desc': 'Search Newznab Indexers', - 'icon': Icons.search, - 'route': '/search', - }, - 'settings': { - 'name': 'Settings', - 'desc': 'Update Configuration', - 'icon': Icons.settings, - 'route': '/settings', - } + static const Map MODULE_MAP = { + LidarrConstants.MODULE_KEY: LidarrConstants.MODULE_MAP, + RadarrConstants.MODULE_KEY: RadarrConstants.MODULE_MAP, + SonarrConstants.MODULE_KEY: SonarrConstants.MODULE_MAP, + NZBGetConstants.MODULE_KEY: NZBGetConstants.MODULE_MAP, + SABnzbdConstants.MODULE_KEY: SABnzbdConstants.MODULE_MAP, + SearchConstants.MODULE_KEY: SearchConstants.MODULE_MAP, + SettingsConstants.MODULE_KEY: SettingsConstants.MODULE_MAP, + WakeOnLANConstants.MODULE_KEY: WakeOnLANConstants.MODULE_MAP, }; //Colors static const PRIMARY_COLOR = 0xFF32323E; @@ -42,15 +22,16 @@ class Constants { static const LIST_COLOR_ICONS = [ Colors.blue, Color(ACCENT_COLOR), - Colors.orange, Colors.red, + Colors.orange, Colors.deepPurpleAccent, Colors.blueGrey, ]; //UI - static const UI_ELEVATION = 2.0; + static const UI_ELEVATION = 0.0; static const UI_CARD_MARGIN = EdgeInsets.symmetric(horizontal: 12.0, vertical: 6.0); static const UI_NAVIGATION_SPEED = 250; + static const UI_BORDER_RADIUS = 12.0; //General static const EMPTY_MAP = {}; static const EMPTY_LIST = []; @@ -73,6 +54,7 @@ class Constants { //Changelog static const EMPTY_CHANGELOG = [{ "version": "Changelog Error", + "date": "Unknown Date", "new": ['Unable to fetch changes'], "fixes": ['Unable to fetch changes'], "tweaks": ['Unable to fetch changes'], diff --git a/lib/core/database/adapters/profile.dart b/lib/core/database/adapters/profile.dart index 0a002e8f..468ae3cb 100644 --- a/lib/core/database/adapters/profile.dart +++ b/lib/core/database/adapters/profile.dart @@ -4,6 +4,8 @@ import 'package:lunasea/core.dart'; part 'profile.g.dart'; +//Next HiveField ID: 31 + @HiveType(typeId: 0, adapterName: 'ProfileHiveObjectAdapter') class ProfileHiveObject extends HiveObject { factory ProfileHiveObject.empty() { @@ -13,22 +15,26 @@ class ProfileHiveObject extends HiveObject { lidarrHost: '', lidarrKey: '', lidarrStrictTLS: true, + lidarrHeaders: {}, //Radarr radarrEnabled: false, radarrHost: '', radarrKey: '', radarrStrictTLS: true, + radarrHeaders: {}, //Sonarr sonarrEnabled: false, sonarrHost: '', sonarrKey: '', sonarrStrictTLS: true, sonarrVersion3: false, + sonarrHeaders: {}, //SABnzbd sabnzbdEnabled: false, sabnzbdHost: '', sabnzbdKey: '', sabnzbdStrictTLS: true, + sabnzbdHeaders: {}, //NZBGet nzbgetEnabled: false, nzbgetHost: '', @@ -36,6 +42,11 @@ class ProfileHiveObject extends HiveObject { nzbgetPass: '', nzbgetStrictTLS: true, nzbgetBasicAuth: false, + nzbgetHeaders: {}, + //Wake on LAN + wakeOnLANEnabled: false, + wakeOnLANBroadcastAddress: '', + wakeOnLANMACAddress: '', ); } @@ -46,22 +57,26 @@ class ProfileHiveObject extends HiveObject { lidarrHost: obj.lidarrHost, lidarrKey: obj.lidarrKey, lidarrStrictTLS: obj.lidarrStrictTLS, + lidarrHeaders: obj.lidarrHeaders, //Radarr radarrEnabled: obj.radarrEnabled, radarrHost: obj.radarrHost, radarrKey: obj.radarrKey, radarrStrictTLS: obj.radarrStrictTLS, + radarrHeaders: obj.radarrHeaders, //Sonarr sonarrEnabled: obj.sonarrEnabled, sonarrHost: obj.sonarrHost, sonarrKey: obj.sonarrKey, sonarrStrictTLS: obj.sonarrStrictTLS, sonarrVersion3: obj.sonarrVersion3, + sonarrHeaders: obj.sonarrHeaders, //SABnzbd sabnzbdEnabled: obj.sabnzbdEnabled, sabnzbdHost: obj.sabnzbdHost, sabnzbdKey: obj.sabnzbdKey, sabnzbdStrictTLS: obj.sabnzbdStrictTLS, + sabnzbdHeaders: obj.sabnzbdHeaders, //NZBGet nzbgetEnabled: obj.nzbgetEnabled, nzbgetHost: obj.nzbgetHost, @@ -69,6 +84,11 @@ class ProfileHiveObject extends HiveObject { nzbgetPass: obj.nzbgetPass, nzbgetStrictTLS: obj.nzbgetStrictTLS, nzbgetBasicAuth: obj.nzbgetBasicAuth, + nzbgetHeaders: obj.nzbgetHeaders, + //Wake On LAN + wakeOnLANEnabled: obj.wakeOnLANEnabled, + wakeOnLANBroadcastAddress: obj.wakeOnLANBroadcastAddress, + wakeOnLANMACAddress: obj.wakeOnLANMACAddress, ); } @@ -78,22 +98,26 @@ class ProfileHiveObject extends HiveObject { @required this.lidarrHost, @required this.lidarrKey, @required this.lidarrStrictTLS, + @required this.lidarrHeaders, //Radarr @required this.radarrEnabled, @required this.radarrHost, @required this.radarrKey, @required this.radarrStrictTLS, + @required this.radarrHeaders, //Sonarr @required this.sonarrEnabled, @required this.sonarrHost, @required this.sonarrKey, @required this.sonarrStrictTLS, @required this.sonarrVersion3, + @required this.sonarrHeaders, //SABnzbd @required this.sabnzbdEnabled, @required this.sabnzbdHost, @required this.sabnzbdKey, @required this.sabnzbdStrictTLS, + @required this.sabnzbdHeaders, //NZBGet @required this.nzbgetEnabled, @required this.nzbgetHost, @@ -101,6 +125,11 @@ class ProfileHiveObject extends HiveObject { @required this.nzbgetPass, @required this.nzbgetStrictTLS, @required this.nzbgetBasicAuth, + @required this.nzbgetHeaders, + //Wake On LAN + @required this.wakeOnLANEnabled, + @required this.wakeOnLANBroadcastAddress, + @required this.wakeOnLANMACAddress, }); @override @@ -117,21 +146,25 @@ class ProfileHiveObject extends HiveObject { "sonarrKey": sonarrKey, "sonarrStrictTLS": sonarrStrictTLS, "sonarrVersion3": sonarrVersion3, + "sonarrHeaders": sonarrHeaders, //Radarr "radarrEnabled": radarrEnabled, "radarrHost": radarrHost, "radarrKey": radarrKey, "radarrStrictTLS": radarrStrictTLS, + "radarrHeaders": radarrHeaders, //Lidarr "lidarrEnabled": lidarrEnabled, "lidarrHost": lidarrHost, "lidarrKey": lidarrKey, "lidarrStrictTLS": lidarrStrictTLS, + "lidarrHeaders": lidarrHeaders, //SABnzbd "sabnzbdEnabled": sabnzbdEnabled, "sabnzbdHost": sabnzbdHost, "sabnzbdKey": sabnzbdKey, "sabnzbdStrictTLS": sabnzbdStrictTLS, + "sabnzbdHeaders": sabnzbdHeaders, //NZBGet "nzbgetEnabled": nzbgetEnabled, "nzbgetHost": nzbgetHost, @@ -139,6 +172,11 @@ class ProfileHiveObject extends HiveObject { "nzbgetPass": nzbgetPass, "nzbgetStrictTLS": nzbgetStrictTLS, "nzbgetBasicAuth": nzbgetBasicAuth, + "nzbgetHeaders": nzbgetHeaders, + //Wake On LAN + "wakeOnLANEnabled": wakeOnLANEnabled, + "wakeOnLANBroadcastAddress": wakeOnLANBroadcastAddress, + "wakeOnLANMACAddress": wakeOnLANMACAddress, }; } @@ -151,12 +189,15 @@ class ProfileHiveObject extends HiveObject { String lidarrKey; @HiveField(18) bool lidarrStrictTLS; + @HiveField(26) + Map lidarrHeaders; Map getLidarr() => { 'enabled': lidarrEnabled ?? false, 'host': lidarrHost ?? '', 'key': lidarrKey ?? '', 'strict_tls': lidarrStrictTLS ?? true, + 'headers': lidarrHeaders ?? {}, }; //Radarr @@ -168,12 +209,15 @@ class ProfileHiveObject extends HiveObject { String radarrKey; @HiveField(17) bool radarrStrictTLS; + @HiveField(27) + Map radarrHeaders; Map getRadarr() => { 'enabled': radarrEnabled ?? false, 'host': radarrHost ?? '', 'key': radarrKey ?? '', 'strict_tls': radarrStrictTLS ?? true, + 'headers': radarrHeaders ?? {}, }; //Sonarr @@ -187,6 +231,8 @@ class ProfileHiveObject extends HiveObject { bool sonarrStrictTLS; @HiveField(21) bool sonarrVersion3; + @HiveField(28) + Map sonarrHeaders; Map getSonarr() => { 'enabled': sonarrEnabled ?? false, @@ -194,6 +240,7 @@ class ProfileHiveObject extends HiveObject { 'key': sonarrKey ?? '', 'strict_tls': sonarrStrictTLS ?? true, 'v3': sonarrVersion3 ?? false, + 'headers': sonarrHeaders ?? {}, }; //SABnzbd @@ -205,12 +252,15 @@ class ProfileHiveObject extends HiveObject { String sabnzbdKey; @HiveField(19) bool sabnzbdStrictTLS; + @HiveField(29) + Map sabnzbdHeaders; Map getSABnzbd() => { 'enabled': sabnzbdEnabled ?? false, 'host': sabnzbdHost ?? '', 'key': sabnzbdKey ?? '', 'strict_tls': sabnzbdStrictTLS ?? true, + 'headers': sabnzbdHeaders ?? {}, }; //NZBGet @@ -226,6 +276,8 @@ class ProfileHiveObject extends HiveObject { bool nzbgetStrictTLS; @HiveField(22) bool nzbgetBasicAuth; + @HiveField(30) + Map nzbgetHeaders; Map getNZBGet() => { 'enabled': nzbgetEnabled ?? false, @@ -234,6 +286,21 @@ class ProfileHiveObject extends HiveObject { 'pass': nzbgetPass ?? '', 'strict_tls': nzbgetStrictTLS ?? true, 'basic_auth': nzbgetBasicAuth ?? false, + 'headers': nzbgetHeaders ?? {}, + }; + + //Wake On LAN + @HiveField(23) + bool wakeOnLANEnabled; + @HiveField(24) + String wakeOnLANBroadcastAddress; + @HiveField(25) + String wakeOnLANMACAddress; + + Map getWakeOnLAN() => { + 'enabled': wakeOnLANEnabled ?? false, + 'broadcastAddress': wakeOnLANBroadcastAddress ?? '', + 'MACAddress': wakeOnLANMACAddress ?? '', }; List get enabledServices => [ diff --git a/lib/core/database/adapters/profile.g.dart b/lib/core/database/adapters/profile.g.dart index 2c0b4760..234d019d 100644 --- a/lib/core/database/adapters/profile.g.dart +++ b/lib/core/database/adapters/profile.g.dart @@ -21,32 +21,40 @@ class ProfileHiveObjectAdapter extends TypeAdapter { lidarrHost: fields[1] as String, lidarrKey: fields[2] as String, lidarrStrictTLS: fields[18] as bool, + lidarrHeaders: (fields[26] as Map)?.cast(), radarrEnabled: fields[3] as bool, radarrHost: fields[4] as String, radarrKey: fields[5] as String, radarrStrictTLS: fields[17] as bool, + radarrHeaders: (fields[27] as Map)?.cast(), sonarrEnabled: fields[6] as bool, sonarrHost: fields[7] as String, sonarrKey: fields[8] as String, sonarrStrictTLS: fields[16] as bool, sonarrVersion3: fields[21] as bool, + sonarrHeaders: (fields[28] as Map)?.cast(), sabnzbdEnabled: fields[9] as bool, sabnzbdHost: fields[10] as String, sabnzbdKey: fields[11] as String, sabnzbdStrictTLS: fields[19] as bool, + sabnzbdHeaders: (fields[29] as Map)?.cast(), nzbgetEnabled: fields[12] as bool, nzbgetHost: fields[13] as String, nzbgetUser: fields[14] as String, nzbgetPass: fields[15] as String, nzbgetStrictTLS: fields[20] as bool, nzbgetBasicAuth: fields[22] as bool, + nzbgetHeaders: (fields[30] as Map)?.cast(), + wakeOnLANEnabled: fields[23] as bool, + wakeOnLANBroadcastAddress: fields[24] as String, + wakeOnLANMACAddress: fields[25] as String, ); } @override void write(BinaryWriter writer, ProfileHiveObject obj) { writer - ..writeByte(23) + ..writeByte(31) ..writeByte(0) ..write(obj.lidarrEnabled) ..writeByte(1) @@ -55,6 +63,8 @@ class ProfileHiveObjectAdapter extends TypeAdapter { ..write(obj.lidarrKey) ..writeByte(18) ..write(obj.lidarrStrictTLS) + ..writeByte(26) + ..write(obj.lidarrHeaders) ..writeByte(3) ..write(obj.radarrEnabled) ..writeByte(4) @@ -63,6 +73,8 @@ class ProfileHiveObjectAdapter extends TypeAdapter { ..write(obj.radarrKey) ..writeByte(17) ..write(obj.radarrStrictTLS) + ..writeByte(27) + ..write(obj.radarrHeaders) ..writeByte(6) ..write(obj.sonarrEnabled) ..writeByte(7) @@ -73,6 +85,8 @@ class ProfileHiveObjectAdapter extends TypeAdapter { ..write(obj.sonarrStrictTLS) ..writeByte(21) ..write(obj.sonarrVersion3) + ..writeByte(28) + ..write(obj.sonarrHeaders) ..writeByte(9) ..write(obj.sabnzbdEnabled) ..writeByte(10) @@ -81,6 +95,8 @@ class ProfileHiveObjectAdapter extends TypeAdapter { ..write(obj.sabnzbdKey) ..writeByte(19) ..write(obj.sabnzbdStrictTLS) + ..writeByte(29) + ..write(obj.sabnzbdHeaders) ..writeByte(12) ..write(obj.nzbgetEnabled) ..writeByte(13) @@ -92,6 +108,14 @@ class ProfileHiveObjectAdapter extends TypeAdapter { ..writeByte(20) ..write(obj.nzbgetStrictTLS) ..writeByte(22) - ..write(obj.nzbgetBasicAuth); + ..write(obj.nzbgetBasicAuth) + ..writeByte(30) + ..write(obj.nzbgetHeaders) + ..writeByte(23) + ..write(obj.wakeOnLANEnabled) + ..writeByte(24) + ..write(obj.wakeOnLANBroadcastAddress) + ..writeByte(25) + ..write(obj.wakeOnLANMACAddress); } } diff --git a/lib/core/dialogs.dart b/lib/core/dialogs.dart new file mode 100644 index 00000000..d50d4935 --- /dev/null +++ b/lib/core/dialogs.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; + +class GlobalDialogs { + GlobalDialogs._(); + + static Future> editText(BuildContext context, String title, { String prefill = '' }) async { + bool _flag = false; + final _formKey = GlobalKey(); + final _textController = TextEditingController()..text = prefill; + + void _setValues(bool flag) { + if(_formKey.currentState.validate()) { + _flag = flag; + Navigator.of(context).pop(); + } + } + + await LSDialog.dialog( + context: context, + title: title, + buttons: [ + LSDialog.button( + text: 'Save', + onPressed: () => _setValues(true), + ), + ], + content: [ + Form( + key: _formKey, + child: LSDialog.textFormInput( + controller: _textController, + title: title, + onSubmitted: (_) => _setValues(true), + validator: (_) => null, + ), + ), + ], + contentPadding: LSDialog.inputDialogContentPadding(), + ); + return [_flag, _textController.text]; + } + + static Future textPreview(BuildContext context, String title, String text, { bool alignLeft = false }) async { + await showDialog( + context: context, + builder: (context) => AlertDialog( + actions: [ + LSDialog.button( + text: 'Close', + onPressed: () => Navigator.of(context).pop(), + ), + ], + title: LSDialog.title(text: title), + content: LSDialog.content( + children: [ + LSDialog.textContent( + text: text, + textAlign: alignLeft + ? TextAlign.left + : TextAlign.center, + ), + ], + ), + contentPadding: LSDialog.textDialogContentPadding(), + shape: LSDialog.shape(), + ), + ); + } + + static Future> deleteCatalogueWithFiles(BuildContext context, String title) async { + bool _flag = false; + + void _setValues(bool flag) { + _flag = flag; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Delete All Files', + buttons: [ + LSDialog.button( + text: 'Delete', + textColor: LSColors.red, + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.textContent(text: 'Are you sure you want to delete all the files and folders for $title?'), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag]; + } +} diff --git a/lib/core/module_flags.dart b/lib/core/module_flags.dart index 8b90a3cc..ca8b932f 100644 --- a/lib/core/module_flags.dart +++ b/lib/core/module_flags.dart @@ -1,12 +1,13 @@ class ModuleFlags { ModuleFlags._(); + //General + static const SEARCH = true; + static const WAKE_ON_LAN = true; //Automation static const AUTOMATION = true; static const LIDARR = true; static const RADARR = true; static const SONARR = true; - //Search - static const SEARCH = true; //Clients static const CLIENTS = true; static const SABNZBD = true; diff --git a/lib/core/networking.dart b/lib/core/networking.dart new file mode 100644 index 00000000..245335ed --- /dev/null +++ b/lib/core/networking.dart @@ -0,0 +1,2 @@ +export './networking/ipv4_address.dart'; +export './networking/mac_address.dart'; diff --git a/lib/core/networking/ipv4_address.dart b/lib/core/networking/ipv4_address.dart new file mode 100644 index 00000000..52bd3937 --- /dev/null +++ b/lib/core/networking/ipv4_address.dart @@ -0,0 +1,18 @@ +class IPv4Address { + static const String _REGEX = r"\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}\b"; + final String _address; + + IPv4Address._internal(this._address); + factory IPv4Address.from(String address) { + if(!IPv4Address.validate(address)) throw FormatException('Not a valid IPv4 address string'); + return IPv4Address._internal(address); + } + + String get address => _address; + + /// Validate that an IPv4 address has been formatted correctly + static bool validate(String address) { + RegExp exp = new RegExp(_REGEX); + return exp.hasMatch(address); + } +} diff --git a/lib/core/networking/mac_address.dart b/lib/core/networking/mac_address.dart new file mode 100644 index 00000000..aee2f3cd --- /dev/null +++ b/lib/core/networking/mac_address.dart @@ -0,0 +1,24 @@ +import 'package:convert/convert.dart'; + +class MacAddress { + static const String _REGEX = r"^([0-9A-Fa-f]{2}[:]){5}([0-9A-Fa-f]{2})$"; + final String _address; + + MacAddress._internal(this._address); + factory MacAddress.from(String address) { + if(!MacAddress.validate(address)) throw FormatException('Not a valid MAC address string'); + return MacAddress._internal(address); + } + + String get address => _address; + List get bytes => _getBytes(); + + //Parse the address string and returns an int array list of 6 values, each containing one block of the + List _getBytes() => _address.split(":").map((octet) => hex.decode(octet)[0]).toList(); + + /// Validate that a MAC address has been formatted correctly + static bool validate(String address) { + RegExp exp = new RegExp(_REGEX); + return exp.hasMatch(address); + } +} \ No newline at end of file diff --git a/lib/core/routes.dart b/lib/core/routes.dart index 00a1637f..90a73df5 100644 --- a/lib/core/routes.dart +++ b/lib/core/routes.dart @@ -42,15 +42,21 @@ class Routes { // /settings/modules/* // Automation SettingsModulesLidarr.ROUTE_NAME: (context) => SettingsModulesLidarr(), + SettingsModulesLidarrHeaders.ROUTE_NAME: (context) => SettingsModulesLidarrHeaders(), SettingsModulesRadarr.ROUTE_NAME: (context) => SettingsModulesRadarr(), + SettingsModulesRadarrHeaders.ROUTE_NAME: (context) => SettingsModulesRadarrHeaders(), SettingsModulesSonarr.ROUTE_NAME: (context) => SettingsModulesSonarr(), + SettingsModulesSonarrHeaders.ROUTE_NAME: (context) => SettingsModulesSonarrHeaders(), // Clients SettingsModulesNZBGet.ROUTE_NAME: (context) => SettingsModulesNZBGet(), + SettingsModulesNZBGetHeaders.ROUTE_NAME: (context) => SettingsModulesNZBGetHeaders(), SettingsModulesSABnzbd.ROUTE_NAME: (context) => SettingsModulesSABnzbd(), + SettingsModulesSABnzbdHeaders.ROUTE_NAME: (context) => SettingsModulesSABnzbdHeaders(), // General SettingsModulesCalendar.ROUTE_NAME: (context) => SettingsModulesCalendar(), SettingsModulesLunaSea.ROUTE_NAME: (context) => SettingsModulesLunaSea(), SettingsModulesSearch.ROUTE_NAME: (context) => SettingsModulesSearch(), + SettingsModulesWakeOnLAN.ROUTE_NAME: (context) => SettingsModulesWakeOnLAN(), // /settings/modules/indexers/* SettingsModulesSearchAdd.ROUTE_NAME: (context) => SettingsModulesSearchAdd(), SettingsModulesSearchEdit.ROUTE_NAME: (context) => SettingsModulesSearchEdit(), @@ -59,6 +65,8 @@ class Routes { SettingsGeneralLogsView.ROUTE_NAME: (context) => SettingsGeneralLogsView(), SettingsGeneralLogsDetails.ROUTE_NAME: (context) => SettingsGeneralLogsDetails(), // /settings/system/* + SettingsSystemChangelog.ROUTE_NAME: (context) => SettingsSystemChangelog(), + SettingsSystemChangelogDetails.ROUTE_NAME: (context) => SettingsSystemChangelogDetails(), SettingsSystemDonations.ROUTE_NAME: (context) => SettingsSystemDonations(), SettingsSystemDonationsThankYou.ROUTE_NAME: (context) => SettingsSystemDonationsThankYou(), }; diff --git a/lib/core/ui/card/card_background.dart b/lib/core/ui/card/card_background.dart index 17b4c4fc..cc2c810a 100644 --- a/lib/core/ui/card/card_background.dart +++ b/lib/core/ui/card/card_background.dart @@ -3,11 +3,12 @@ import 'package:flutter_advanced_networkimage/provider.dart'; import 'package:lunasea/core.dart'; // ignore: non_constant_identifier_names -Decoration LSCardBackground({ @required String uri, bool darken = false }) => BoxDecoration( +Decoration LSCardBackground({ @required String uri, @required Map headers, bool darken = false }) => BoxDecoration( image: DecorationImage( image: AdvancedNetworkImage( uri, useDiskCache: true, + header: Map.from(headers), loadFailedCallback: () {}, fallbackAssetImage: 'assets/images/colors/secondary.png', retryLimit: 1, diff --git a/lib/core/ui/description_block.dart b/lib/core/ui/description_block.dart index 4d69893a..821d404c 100644 --- a/lib/core/ui/description_block.dart +++ b/lib/core/ui/description_block.dart @@ -8,12 +8,14 @@ class LSDescriptionBlock extends StatefulWidget { final String title; final String uri; final String fallbackImage; + final Map headers; LSDescriptionBlock({ @required this.description, @required this.title, @required this.uri, @required this.fallbackImage, + @required this.headers, }); @override @@ -32,6 +34,7 @@ class _State extends State { child: TransitionToImage( image: AdvancedNetworkImage( widget.uri, + header: Map.from(widget.headers), useDiskCache: true, fallbackAssetImage: widget.fallbackImage, retryLimit: 1, @@ -67,7 +70,7 @@ class _State extends State { ], ), borderRadius: BorderRadius.all(Radius.circular(4.0)), - onTap: () => LSDialogSystem.textPreview(context, widget.title, widget.description.trim() ?? 'No summary is available.'), + onTap: () => GlobalDialogs.textPreview(context, widget.title, widget.description.trim() ?? 'No summary is available.'), ), ); } diff --git a/lib/core/ui/dialogs.dart b/lib/core/ui/dialogs.dart index 4f023e8b..a85a1b15 100644 --- a/lib/core/ui/dialogs.dart +++ b/lib/core/ui/dialogs.dart @@ -1,13 +1,3 @@ -//Export -export './dialogs/system.dart'; -export './dialogs/lidarr.dart'; -export './dialogs/radarr.dart'; -export './dialogs/sonarr.dart'; -export './dialogs/nzbget.dart'; -export './dialogs/sabnzbd.dart'; -export './dialogs/settings.dart'; -export './dialogs/search.dart'; -//Import import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; @@ -16,14 +6,38 @@ abstract class LSDialog { text, textAlign: TextAlign.center, style: TextStyle( + fontSize: 18.0, fontWeight: FontWeight.bold, ), ); - static Widget button({ @required String text, @required void Function() onPressed, Color textColor = Colors.white }) => FlatButton( + static TextSpan bolded({ @required String text, double fontSize = 14.0, Color color }) => TextSpan( + text: text, + style: TextStyle( + color: color == null + ? LSColors.accent + : color, + fontWeight: FontWeight.bold, + fontSize: fontSize, + ), + ); + + static Widget richText({ @required List children, TextAlign alignment = TextAlign.start }) => RichText( + text: TextSpan( + children: children, + ), + textAlign: alignment, + ); + + static Widget button({ @required String text, @required void Function() onPressed, Color textColor }) => FlatButton( child: Text( text, - style: TextStyle(color: textColor), + style: TextStyle( + color: textColor == null + ? LSColors.accent + : textColor, + fontSize: 12.0, + ), ), onPressed: onPressed, ); @@ -31,7 +45,10 @@ abstract class LSDialog { static Widget cancel(BuildContext context, { Color textColor = Colors.white }) => FlatButton( child: Text( 'Cancel', - style: TextStyle(color: textColor), + style: TextStyle( + color: textColor, + fontSize: 12.0, + ), ), onPressed: () => Navigator.of(context).pop(), ); @@ -40,13 +57,14 @@ abstract class LSDialog { child: ListBody( children: children, ), - padding: EdgeInsets.zero, + padding: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 1.0), ); static Widget textContent({ @required String text, TextAlign textAlign = TextAlign.center }) => Text( text, style: TextStyle( color: Colors.white, + fontSize: 14.0, ), textAlign: textAlign, ); @@ -55,7 +73,7 @@ abstract class LSDialog { text: text, style: TextStyle( color: Colors.white, - fontSize: 16.0, + fontSize: 14.0, ), ); @@ -72,48 +90,123 @@ abstract class LSDialog { labelStyle: TextStyle( color: Colors.white54, decoration: TextDecoration.none, + fontSize: 14.0, ), focusedBorder: UnderlineInputBorder( borderSide: BorderSide(color: LSColors.accent), ), enabledBorder: UnderlineInputBorder( - borderSide: BorderSide(color: LSColors.accent), + borderSide: BorderSide(color: LSColors.accent.withOpacity(0.3)), ), ), - style: TextStyle(color: Colors.white), + style: TextStyle( + color: Colors.white, + fontSize: 14.0, + ), cursorColor: LSColors.accent, textInputAction: TextInputAction.done, onSubmitted: onSubmitted, ); + static Widget textFormInput({ + @required TextEditingController controller, + @required String title, + @required Function(String) onSubmitted, + @required Function(String) validator, + bool obscureText = false, + TextInputType keyboardType = TextInputType.text, + }) => TextFormField( + autofocus: true, + autocorrect: false, + controller: controller, + obscureText: obscureText, + keyboardType: keyboardType, + decoration: InputDecoration( + labelText: title, + labelStyle: TextStyle( + color: Colors.white54, + decoration: TextDecoration.none, + fontSize: 14.0, + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: LSColors.accent), + ), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide(color: LSColors.accent.withOpacity(0.3)), + ), + ), + style: TextStyle( + color: Colors.white, + fontSize: 14.0, + ), + cursorColor: LSColors.accent, + textInputAction: TextInputAction.done, + validator: validator, + onFieldSubmitted: onSubmitted, + ); + static Widget tile({ - @required bool icon, - IconData iconData, + @required IconData icon, Color iconColor, @required String text, + RichText subtitle, @required Function onTap, }) => ListTile( - leading: Icon( - iconData ?? Icons.error_outline, - color: iconColor ?? LSColors.accent, + leading: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + LSIcon( + icon: icon ?? Icons.error_outline, + color: iconColor ?? LSColors.accent, + ), + ], ), title: Text( text, style: TextStyle( + fontSize: 14.0, color: Colors.white, ), ), + subtitle: text == null + ? null + : subtitle, onTap: onTap, + contentPadding: tileContentPadding(), ); - static TextSpan bolded({ @required String title, double fontSize = 14.0, Color color }) => TextSpan( - text: title, - style: TextStyle( - color: color == null - ? LSColors.accent - : color, - fontWeight: FontWeight.bold, - fontSize: fontSize, - ), + static EdgeInsets tileContentPadding() => EdgeInsets.fromLTRB(32.0, 0.0, 16.0, 0.0); + static EdgeInsets textDialogContentPadding() => EdgeInsets.fromLTRB(24.0, 36.0, 24.0, 14.0); + static EdgeInsets listDialogContentPadding() => EdgeInsets.fromLTRB(0.0, 26.0, 24.0, 0.0); + static EdgeInsets inputTextDialogContentPadding() => EdgeInsets.fromLTRB(24.0, 36.0, 24.0, 22.0); + static EdgeInsets inputDialogContentPadding() => EdgeInsets.fromLTRB(24.0, 22.0, 24.0, 22.0); + + static ShapeBorder shape() => RoundedRectangleBorder( + borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), ); + + static Future dialog({ + @required BuildContext context, + @required String title, + @required List content, + @required EdgeInsets contentPadding, + List buttons, + }) async { + await showDialog( + context: context, + builder: (context) => AlertDialog( + actions: [ + LSDialog.cancel( + context, + textColor: buttons != null ? Colors.white : LSColors.accent, + ), + if(buttons != null) ...buttons, + ], + title: LSDialog.title(text: title), + content: LSDialog.content(children: content), + contentPadding: contentPadding, + shape: LSDialog.shape(), + ), + ); + } } diff --git a/lib/core/ui/dialogs/lidarr.dart b/lib/core/ui/dialogs/lidarr.dart deleted file mode 100644 index 5d812b1b..00000000 --- a/lib/core/ui/dialogs/lidarr.dart +++ /dev/null @@ -1,607 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/lidarr.dart'; - -class LSDialogLidarr { - LSDialogLidarr._(); - - static Future> showEditQualityProfilePrompt(BuildContext context, List qualities) async { - bool flag = false; - LidarrQualityProfile quality; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Quality Profile', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: Container( - child: ListView.builder( - shrinkWrap: true, - itemCount: qualities.length, - itemBuilder: (BuildContext context, int index) { - return ListTile( - title: Text( - qualities[index].name, - style: TextStyle( - color: Colors.white, - ), - ), - leading: Icon( - Icons.portrait, - color: Constants.LIST_COLOR_ICONS[index%Constants.LIST_COLOR_ICONS.length], - ), - onTap: () { - quality = qualities[index]; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ); - }, - ), - width: 400, - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 32.0, 0.0), - ); - } - ); - return [flag, quality]; - } - - static Future> showEditMetadataProfilePrompt(BuildContext context, List metadatas) async { - bool flag = false; - LidarrMetadataProfile metadata; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Metadata Profile', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: Container( - child: ListView.builder( - shrinkWrap: true, - itemCount: metadatas.length, - itemBuilder: (BuildContext context, int index) { - return ListTile( - title: Text( - metadatas[index].name, - style: TextStyle( - color: Colors.white, - ), - ), - leading: Icon( - Icons.portrait, - color: Constants.LIST_COLOR_ICONS[index%Constants.LIST_COLOR_ICONS.length], - ), - onTap: () { - metadata = metadatas[index]; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ); - }, - ), - width: 400, - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 32.0, 0.0), - ); - } - ); - return [flag, metadata]; - } - - static Future> showDeleteArtistPrompt(BuildContext context) async { - bool flag = false; - bool files = false; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Remove Artist', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Remove + Files', - style: TextStyle( - color: Colors.red, - ), - ), - onPressed: () { - flag = true; - files = true; - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Remove', - style: TextStyle( - color: Colors.red, - ), - ), - onPressed: () { - flag = true; - files = false; - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: Text( - 'Are you sure you want to remove the artist from Lidarr?', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ), - ); - } - ); - return [flag, files]; - } - - static Future> showDownloadWarningPrompt(BuildContext context) async { - bool flag = false; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Download Release', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Download', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - flag = true; - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - Text( - 'Are you sure you want to download this release? It has been marked as a rejected release by Lidarr.', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - ], - ), - padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ), - ); - } - ); - return [flag]; - } - - static Future> showSearchMissingPrompt(BuildContext context) async { - bool flag = false; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Search All Missing', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Search', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - flag = true; - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: Text( - 'Are you sure you want to search for all missing albums?', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ), - ); - } - ); - return [flag]; - } - - static Future> showEditArtistPrompt(BuildContext context, LidarrCatalogueData entry) async { - bool flag = false; - String value = ''; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - entry.title, - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - ListTile( - leading: Icon( - Icons.edit, - color: Colors.blue, - ), - title: Text( - 'Edit Artist', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'edit_artist'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.refresh, - color: Color(Constants.ACCENT_COLOR), - ), - title: Text( - 'Refresh Artist', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'refresh_artist'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.delete, - color: Colors.red, - ), - title: Text( - 'Remove Artist', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'remove_artist'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ], - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, value]; - } - - static Future> showEditRootFolderPrompt(BuildContext context, List folders) async { - bool flag = false; - LidarrRootFolder folder; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Root Folder', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: Container( - child: ListView.builder( - shrinkWrap: true, - itemCount: folders.length, - itemBuilder: (BuildContext context, int index) { - return ListTile( - title: Text( - folders[index].path, - style: TextStyle( - color: Colors.white, - ), - ), - subtitle: Text( - folders[index].freeSpace.lsBytes_BytesToString(), - style: TextStyle( - color: LSColors.accent, - fontWeight: FontWeight.bold, - ), - ), - leading: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - LSIcon( - icon: Icons.folder, - color: Constants.LIST_COLOR_ICONS[index%Constants.LIST_COLOR_ICONS.length], - ), - ], - ), - onTap: () { - folder = folders[index]; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ); - }, - ), - width: 400, - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 32.0, 0.0), - ); - } - ); - return [flag, folder]; - } - - static Future> showSettingsPrompt(BuildContext context) async { - bool flag = false; - String value = ''; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Lidarr Settings', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - ListTile( - leading: Icon( - Icons.language, - color: Colors.blue, - ), - title: Text( - 'View Web GUI', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'web_gui'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.autorenew, - color: Color(Constants.ACCENT_COLOR), - ), - title: Text( - 'Update Library', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'update_library'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.rss_feed, - color: Colors.orange, - ), - title: Text( - 'Run RSS Sync', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'rss_sync'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.search, - color: Colors.red, - ), - title: Text( - 'Search All Missing', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'missing_search'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.save, - color: Colors.deepPurpleAccent, - ), - title: Text( - 'Backup Database', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'backup'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ], - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, value]; - } -} diff --git a/lib/core/ui/dialogs/nzbget.dart b/lib/core/ui/dialogs/nzbget.dart deleted file mode 100644 index 5640fd92..00000000 --- a/lib/core/ui/dialogs/nzbget.dart +++ /dev/null @@ -1,1574 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/nzbget.dart'; - -class LSDialogNZBGet { - LSDialogNZBGet._(); - - static Future> showSettingsPrompt(BuildContext context) async { - bool flag = false; - String value = ''; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'NZBGet Settings', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - ListTile( - leading: Icon( - Icons.language, - color: Colors.blue, - ), - title: Text( - 'View Web GUI', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'web_gui'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.add, - color: Color(Constants.ACCENT_COLOR), - ), - title: Text( - 'Add NZB', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'add_nzb'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.sort, - color: Colors.orange, - ), - title: Text( - 'Sort Queue', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'sort'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.info_outline, - color: Colors.red, - ), - title: Text( - 'Status & Statistics', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'server_details'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ], - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, value]; - } - - static Future> showQueueSettingsPrompt(BuildContext context, String title, bool isPaused) async { - bool flag = false; - String value = ''; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - title, - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - ListTile( - leading: Icon( - isPaused ? Icons.play_arrow : Icons.pause, - color: Colors.blue, - ), - title: Text( - isPaused ? 'Resume Job' : 'Pause Job', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'status'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.category, - color: Color(Constants.ACCENT_COLOR), - ), - title: Text( - 'Change Category', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'category'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.low_priority, - color: Colors.orange, - ), - title: Text( - 'Change Priority', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'priority'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.vpn_key, - color: Colors.red, - ), - title: Text( - 'Set Password', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'password'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.text_format, - color: Colors.deepPurpleAccent, - ), - title: Text( - 'Rename Job', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'rename'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.delete, - color: Colors.blueGrey, - ), - title: Text( - 'Delete Job', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'delete'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ], - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, value]; - } - - static Future> showDeleteJobPrompt(BuildContext context) async { - bool flag = false; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Delete Job', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Delete', - style: TextStyle( - color: Colors.red, - ), - ), - onPressed: () { - flag = true; - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: Text( - 'Are you sure you want to delete this job?', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ), - ); - } - ); - return [flag]; - } - - static Future> showRenameJobPrompt(BuildContext context, String originalName) async { - bool flag = false; - final formKey = GlobalKey(); - final textController = TextEditingController(); - textController.text = originalName; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Rename Job', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Rename', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - if(formKey.currentState.validate()) { - flag = true; - Navigator.of(context).pop(); - } - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - Text( - 'Please enter a new name for this job.', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - Form( - key: formKey, - child: TextFormField( - autofocus: true, - autocorrect: false, - controller: textController, - decoration: InputDecoration( - labelText: 'Name', - labelStyle: TextStyle( - color: Colors.white54, - decoration: TextDecoration.none, - ), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Color(Constants.ACCENT_COLOR), - ), - ), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Color(Constants.ACCENT_COLOR), - ), - ), - ), - style: TextStyle( - color: Colors.white, - ), - cursorColor: Color(Constants.ACCENT_COLOR), - validator: (value) { - if(value.length < 1) { - return 'Please enter a valid name'; - } - return null; - }, - ), - ), - ], - ), - ), - ); - } - ); - return [flag, textController.text]; - } - - static Future> showChangePriorityPrompt(BuildContext context) async { - bool flag = false; - NZBGetPriority priority; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Change Priority', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: NZBGetPriority.values.expand((entry) => { - ListTile( - leading: Icon( - Icons.low_priority, - color: Constants.LIST_COLOR_ICONS[entry.index % Constants.LIST_COLOR_ICONS.length], - ), - title: Text( - entry.name, - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - priority = entry; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ) - }).toList(), - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, priority]; - } - - static Future> showCategoryPrompt(BuildContext context, List categories) async { - bool flag = false; - NZBGetCategoryData entry; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Change Category', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: List.generate( - categories.length, - (index) => ListTile( - leading: Icon( - Icons.category, - color: Constants.LIST_COLOR_ICONS[index%Constants.LIST_COLOR_ICONS.length], - ), - title: Text( - categories[index].name == '' ? 'No Category' : categories[index].name, - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - entry = categories[index]; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ) - ), - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, entry]; - } - - static Future> showSetPasswordPrompt(BuildContext context) async { - bool flag = false; - final formKey = GlobalKey(); - final textController = TextEditingController(); - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Set Password', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Set', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - if(formKey.currentState.validate()) { - flag = true; - Navigator.of(context).pop(); - } - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - Text( - 'Please enter a password for this job.', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - Form( - key: formKey, - child: TextFormField( - autofocus: true, - autocorrect: false, - obscureText: true, - controller: textController, - decoration: InputDecoration( - labelText: 'Password', - labelStyle: TextStyle( - color: Colors.white54, - decoration: TextDecoration.none, - ), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Color(Constants.ACCENT_COLOR), - ), - ), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Color(Constants.ACCENT_COLOR), - ), - ), - ), - style: TextStyle( - color: Colors.white, - ), - cursorColor: Color(Constants.ACCENT_COLOR), - validator: (value) { - if(value.length < 1) { - return 'Please enter a valid password'; - } - return null; - }, - ), - ), - ], - ), - ), - ); - } - ); - return [flag, textController.text]; - } - - static Future> showDeleteHistoryPrompt(BuildContext context) async { - bool flag = false; - bool hide = false; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Remove History', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Hide', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - flag = true; - hide = true; - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Delete', - style: TextStyle( - color: Colors.red, - ), - ), - onPressed: () { - flag = true; - hide = false; - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: Text( - 'Are you sure you want to remove the history for this job?', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ), - ); - } - ); - return [flag, hide]; - } - - static Future> showSortPrompt(BuildContext context) async { - bool flag = false; - NZBGetSort sort; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Sort Queue', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: List.generate( - NZBGetSort.values.length, - (index) => ListTile( - leading: Icon( - NZBGetSort.values[index].icon, - color: Constants.LIST_COLOR_ICONS[index%Constants.LIST_COLOR_ICONS.length], - ), - title: Text( - NZBGetSort.values[index].name, - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - sort = NZBGetSort.values[index]; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ), - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, sort]; - } - - static Future> showAddNZBPrompt(BuildContext context) async { - bool flag = false; - String type = ''; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Add NZB', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - ListTile( - leading: Icon( - Icons.link, - color: Colors.blue, - ), - title: Text( - 'Add by URL', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - type = 'link'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.sd_card, - color: Color(Constants.ACCENT_COLOR), - ), - title: Text( - 'Add by File', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - type = 'file'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ], - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, type]; - } - - static Future> showaddURLPrompt(BuildContext context) async { - bool flag = false; - final formKey = GlobalKey(); - final textController = TextEditingController(); - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Add NZB by URL', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Add', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - if(formKey.currentState.validate()) { - flag = true; - Navigator.of(context).pop(); - } - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - Text( - 'Please enter the URL of the NZB file you want to add.', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - Form( - key: formKey, - child: TextFormField( - autofocus: true, - autocorrect: false, - controller: textController, - decoration: InputDecoration( - labelText: 'NZB URL', - labelStyle: TextStyle( - color: Colors.white54, - decoration: TextDecoration.none, - ), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Color(Constants.ACCENT_COLOR), - ), - ), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Color(Constants.ACCENT_COLOR), - ), - ), - ), - style: TextStyle( - color: Colors.white, - ), - cursorColor: Color(Constants.ACCENT_COLOR), - validator: (value) { - if(!value.startsWith('http://') && !value.startsWith('https://')) { - return 'Please enter a valid URL'; - } - return null; - }, - ), - ), - ], - ), - ), - ); - } - ); - return [flag, textController.text]; - } - - static Future> showSpeedPrompt(BuildContext context, String currentSpeed) async { - bool flag = false; - int limit = 0; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - currentSpeed == 'Unlimited' ? 'Speed Limit (Unlimited)' : 'Speed Limit ($currentSpeed/s)', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - ListTile( - leading: Icon( - Icons.timeline, - color: Colors.blue, - ), - title: Text( - 'Unlimited', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - limit = 0; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.timeline, - color: Color(Constants.ACCENT_COLOR), - ), - title: Text( - '125 MB/s', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - limit = 128000; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.timeline, - color: Colors.orange, - ), - title: Text( - '100 MB/s', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - limit = 102400; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.timeline, - color: Colors.red, - ), - title: Text( - '75 MB/s', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - limit = 76800; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.timeline, - color: Colors.deepPurpleAccent, - ), - title: Text( - '50 MB/s', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - limit = 51200; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.timeline, - color: Colors.blueGrey, - ), - title: Text( - '25 MB/s', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - limit = 25600; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.timeline, - color: Color(Constants.ACCENT_COLOR), - ), - title: Text( - '10 MB/s', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - limit = 10240; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.timeline, - color: Colors.blue, - ), - title: Text( - 'Custom', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - limit = -1; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ], - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, limit]; - } - - static Future> showCustomSpeedPrompt(BuildContext context) async { - bool flag = false; - final formKey = GlobalKey(); - final textController = TextEditingController(); - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Custom Speed Limit', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Set', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - if(formKey.currentState.validate()) { - flag = true; - Navigator.of(context).pop(); - } - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - Text( - 'Please enter a speed limit in KB/s.', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - Form( - key: formKey, - child: TextFormField( - autofocus: true, - autocorrect: false, - keyboardType: TextInputType.number, - controller: textController, - decoration: InputDecoration( - labelText: 'Speed Limit', - labelStyle: TextStyle( - color: Colors.white54, - decoration: TextDecoration.none, - ), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Color(Constants.ACCENT_COLOR), - ), - ), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Color(Constants.ACCENT_COLOR), - ), - ), - ), - style: TextStyle( - color: Colors.white, - ), - cursorColor: Color(Constants.ACCENT_COLOR), - validator: (value) { - int _value = int.tryParse(value); - if(_value == null || _value < 1) { - return 'Must be a number greater than 1'; - } - return null; - }, - ), - ), - ], - ), - ), - ); - } - ); - return [flag, int.tryParse(textController.text)]; - } - - static Future> showHistorySettingsPrompt(BuildContext context, String title) async { - bool flag = false; - String value = ''; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - title, - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - maxLines: 1, - softWrap: false, - overflow: TextOverflow.fade, - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - ListTile( - leading: Icon( - Icons.autorenew, - color: Colors.blue, - ), - title: Text( - 'Retry Job', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'retry'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.visibility_off, - color: Colors.orange, - ), - title: Text( - 'Hide History', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'hide'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.delete, - color: Colors.red, - ), - title: Text( - 'Delete History', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'delete'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ], - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, value]; - } - - static Future> showPauseForPrompt(BuildContext context) async { - bool flag = false; - int duration = 0; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Pause Queue For...', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - ListTile( - leading: Icon( - Icons.access_time, - color: Colors.blue, - ), - title: Text( - '5 Minutes', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - duration = 5; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.access_time, - color: Color(Constants.ACCENT_COLOR), - ), - title: Text( - '15 Minutes', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - duration = 15; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.access_time, - color: Colors.orange, - ), - title: Text( - '30 Minutes', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - duration = 30; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.access_time, - color: Colors.red, - ), - title: Text( - '1 Hour', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - duration = 60; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.access_time, - color: Colors.deepPurpleAccent, - ), - title: Text( - '3 Hours', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - duration = 180; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.access_time, - color: Colors.blueGrey, - ), - title: Text( - '6 Hours', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - duration = 360; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.access_time, - color: Colors.blue, - ), - title: Text( - 'Custom', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - duration = -1; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ], - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, duration]; - } - - static Future> showCustomPauseForPrompt(BuildContext context) async { - bool flag = false; - final formKey = GlobalKey(); - final textController = TextEditingController(); - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Custom Pause Duration', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Pause', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - if(formKey.currentState.validate()) { - flag = true; - Navigator.of(context).pop(); - } - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - Text( - 'Please enter how long in minutes you want to pause the queue for.', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - Form( - key: formKey, - child: TextFormField( - autofocus: true, - autocorrect: false, - controller: textController, - decoration: InputDecoration( - labelText: 'Pause Duration', - labelStyle: TextStyle( - color: Colors.white54, - decoration: TextDecoration.none, - ), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Color(Constants.ACCENT_COLOR), - ), - ), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Color(Constants.ACCENT_COLOR), - ), - ), - ), - style: TextStyle( - color: Colors.white, - ), - cursorColor: Color(Constants.ACCENT_COLOR), - validator: (value) { - int _value = int.tryParse(value); - if(_value == null || _value < 1) { - return 'Must be at least 1'; - } - return null; - }, - ), - ), - ], - ), - ), - ); - } - ); - return [flag, int.tryParse(textController.text)]; - } -} diff --git a/lib/core/ui/dialogs/radarr.dart b/lib/core/ui/dialogs/radarr.dart deleted file mode 100644 index bf485d95..00000000 --- a/lib/core/ui/dialogs/radarr.dart +++ /dev/null @@ -1,661 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/radarr.dart'; - -class LSDialogRadarr { - LSDialogRadarr._(); - - static Future> showDeleteMovieFilePrompt(BuildContext context) async { - bool flag = false; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Delete Movie File', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Delete', - style: TextStyle( - color: Colors.red, - ), - ), - onPressed: () { - flag = true; - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: Text( - 'Are you sure you want to delete this movie file?', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ), - ); - } - ); - return [flag]; - } - - static Future> showDeleteMoviePrompt(BuildContext context) async { - bool flag = false; - bool files = false; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Remove Movie', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Remove + Files', - style: TextStyle( - color: Colors.red, - ), - ), - onPressed: () { - flag = true; - files = true; - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Remove', - style: TextStyle( - color: Colors.red, - ), - ), - onPressed: () { - flag = true; - files = false; - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: Text( - 'Are you sure you want to remove the movie from Radarr?', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ), - ); - } - ); - return [flag, files]; - } - - static Future> showSearchMissingPrompt(BuildContext context) async { - bool flag = false; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Search For All Movies', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Search', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - flag = true; - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: Text( - 'Are you sure you want to search for all missing movies?', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ), - ); - } - ); - return [flag]; - } - - static Future> showEditMoviePrompt(BuildContext context, RadarrCatalogueData entry) async { - bool flag = false; - String value = ''; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - entry.title, - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - ListTile( - leading: Icon( - Icons.edit, - color: Colors.blue, - ), - title: Text( - 'Edit Movie', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'edit_movie'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.refresh, - color: Color(Constants.ACCENT_COLOR), - ), - title: Text( - 'Refresh Movie', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'refresh_movie'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.delete, - color: Colors.red, - ), - title: Text( - 'Remove Movie', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'remove_movie'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ], - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, value]; - } - - static Future> showMinimumAvailabilityPrompt(BuildContext context, List availability) async { - bool flag = false; - RadarrAvailability entry; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Minimum Availability', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: Container( - child: ListView.builder( - shrinkWrap: true, - itemCount: availability.length, - itemBuilder: (BuildContext context, int index) { - return ListTile( - title: Text( - availability[index].name, - style: TextStyle( - color: Colors.white, - ), - ), - leading: Icon( - Icons.folder, - color: Constants.LIST_COLOR_ICONS[index%Constants.LIST_COLOR_ICONS.length], - ), - onTap: () { - entry = availability[index]; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ); - }, - ), - width: 400, - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 32.0, 0.0), - ); - } - ); - return [flag, entry]; - } - - static Future> showEditRootFolderPrompt(BuildContext context, List folders) async { - bool flag = false; - RadarrRootFolder folder; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Root Folder', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: Container( - child: ListView.builder( - shrinkWrap: true, - itemCount: folders.length, - itemBuilder: (BuildContext context, int index) { - return ListTile( - title: Text( - folders[index].path, - style: TextStyle( - color: Colors.white, - ), - ), - subtitle: Text( - folders[index].freeSpace.lsBytes_BytesToString(), - style: TextStyle( - color: LSColors.accent, - fontWeight: FontWeight.bold, - ), - ), - leading: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - LSIcon( - icon: Icons.folder, - color: Constants.LIST_COLOR_ICONS[index%Constants.LIST_COLOR_ICONS.length], - ), - ], - ), - onTap: () { - folder = folders[index]; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ); - }, - ), - width: 400, - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 32.0, 0.0), - ); - } - ); - return [flag, folder]; - } - - static Future> showEditQualityProfilePrompt(BuildContext context, List qualities) async { - bool flag = false; - RadarrQualityProfile quality; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Quality Profile', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: Container( - child: ListView.builder( - shrinkWrap: true, - itemCount: qualities.length, - itemBuilder: (BuildContext context, int index) { - return ListTile( - title: Text( - qualities[index].name, - style: TextStyle( - color: Colors.white, - ), - ), - leading: Icon( - Icons.portrait, - color: Constants.LIST_COLOR_ICONS[index%Constants.LIST_COLOR_ICONS.length], - ), - onTap: () { - quality = qualities[index]; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ); - }, - ), - width: 400, - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 32.0, 0.0), - ); - } - ); - return [flag, quality]; - } - - static Future> showDownloadWarningPrompt(BuildContext context) async { - bool flag = false; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Download Release', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Download', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - flag = true; - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - Text( - 'Are you sure you want to download this release? It has been marked as a rejected release by Radarr.', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - ], - ), - padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ), - ); - } - ); - return [flag]; - } - - static Future> showSettingsPrompt(BuildContext context) async { - bool flag = false; - String value = ''; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Radarr Settings', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - ListTile( - leading: Icon( - Icons.language, - color: Colors.blue, - ), - title: Text( - 'View Web GUI', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'web_gui'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.autorenew, - color: Color(Constants.ACCENT_COLOR), - ), - title: Text( - 'Update Library', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'update_library'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.rss_feed, - color: Colors.orange, - ), - title: Text( - 'Run RSS Sync', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'rss_sync'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.search, - color: Colors.red, - ), - title: Text( - 'Search All Missing', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'missing_search'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.save, - color: Colors.deepPurpleAccent, - ), - title: Text( - 'Backup Database', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'backup'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ], - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, value]; - } -} diff --git a/lib/core/ui/dialogs/sabnzbd.dart b/lib/core/ui/dialogs/sabnzbd.dart deleted file mode 100644 index 08c98da6..00000000 --- a/lib/core/ui/dialogs/sabnzbd.dart +++ /dev/null @@ -1,2013 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sabnzbd.dart'; - -class LSDialogSABnzbd { - LSDialogSABnzbd._(); - - static Future> showSettingsPrompt(BuildContext context) async { - bool flag = false; - String value = ''; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'SABnzbd Settings', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - ListTile( - leading: Icon( - Icons.language, - color: Colors.blue, - ), - title: Text( - 'View Web GUI', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'web_gui'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.add, - color: Color(Constants.ACCENT_COLOR), - ), - title: Text( - 'Add NZB', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'add_nzb'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.sort, - color: Colors.orange, - ), - title: Text( - 'Sort Queue', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'sort'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.clear_all, - color: Colors.red, - ), - title: Text( - 'Clear History', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'clear_history'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.settings_power, - color: Colors.deepPurpleAccent, - ), - title: Text( - 'On Complete Action', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'complete_action'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.info_outline, - color: Colors.blueGrey, - ), - title: Text( - 'Status & Statistics', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'server_details'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ], - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, value]; - } - - static Future> showQueueSettingsPrompt(BuildContext context, String title, bool isPaused) async { - bool flag = false; - String value = ''; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - title, - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - ListTile( - leading: Icon( - isPaused ? Icons.play_arrow : Icons.pause, - color: Colors.blue, - ), - title: Text( - isPaused ? 'Resume Job' : 'Pause Job', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'status'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.category, - color: Color(Constants.ACCENT_COLOR), - ), - title: Text( - 'Change Category', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'category'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.low_priority, - color: Colors.orange, - ), - title: Text( - 'Change Priority', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'priority'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.vpn_key, - color: Colors.red, - ), - title: Text( - 'Set Password', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'password'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.text_format, - color: Colors.deepPurpleAccent, - ), - title: Text( - 'Rename Job', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'rename'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.delete, - color: Colors.blueGrey, - ), - title: Text( - 'Delete Job', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'delete'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ], - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, value]; - } - - static Future> showHistorySettingsPrompt(BuildContext context, String title, bool failed) async { - bool flag = false; - String value = ''; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - title, - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - maxLines: 1, - softWrap: false, - overflow: TextOverflow.fade, - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - if(failed) ListTile( - leading: Icon( - Icons.autorenew, - color: Colors.blue, - ), - title: Text( - 'Retry Job', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'retry'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - if(failed) ListTile( - leading: Icon( - Icons.vpn_key, - color: Color(Constants.ACCENT_COLOR), - ), - title: Text( - 'Set Password', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'password'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.delete, - color: Colors.red, - ), - title: Text( - 'Delete History', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'delete'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ], - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, value]; - } - - static Future> showCategoryPrompt(BuildContext context, List categories) async { - bool flag = false; - String value = ''; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Change Category', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: List.generate( - categories.length, - (index) => ListTile( - leading: Icon( - Icons.category, - color: Constants.LIST_COLOR_ICONS[index%Constants.LIST_COLOR_ICONS.length], - ), - title: Text( - categories[index].category, - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = categories[index].category; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ) - ), - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, value]; - } - - static Future> showSortPrompt(BuildContext context) async { - bool flag = false; - String sort = ''; - String dir = ''; - String name = ''; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Sort Queue', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - ListTile( - leading: Icon( - Icons.access_time, - color: Colors.blue, - ), - title: Text( - 'Age (Ascending)', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - sort = 'avg_age'; - dir = 'asc'; - name = 'Age (Ascending)'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.access_time, - color: Color(Constants.ACCENT_COLOR), - ), - title: Text( - 'Age (Descending)', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - sort = 'avg_age'; - dir = 'desc'; - name = 'Age (Descending)'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.text_rotate_vertical, - color: Colors.orange, - ), - title: Text( - 'Name (Ascending)', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - sort = 'name'; - dir = 'asc'; - name = 'Name (Ascending)'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.text_rotate_vertical, - color: Colors.red, - ), - title: Text( - 'Name (Descending)', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - sort = 'name'; - dir = 'desc'; - name = 'Name (Descending)'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.sd_card, - color: Colors.deepPurpleAccent, - ), - title: Text( - 'Size (Ascending)', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - sort = 'size'; - dir = 'asc'; - name = 'Size (Ascending)'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.sd_card, - color: Colors.blueGrey, - ), - title: Text( - 'Size (Descending)', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - sort = 'size'; - dir = 'desc'; - name = 'Size (Descending)'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ], - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, sort, dir, name]; - } - - static Future> showAddNZBPrompt(BuildContext context) async { - bool flag = false; - String type = ''; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Add NZB', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - ListTile( - leading: Icon( - Icons.link, - color: Colors.blue, - ), - title: Text( - 'Add by URL', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - type = 'link'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.sd_card, - color: Color(Constants.ACCENT_COLOR), - ), - title: Text( - 'Add by File', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - type = 'file'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ], - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, type]; - } - - static Future> showaddURLPrompt(BuildContext context) async { - bool flag = false; - final formKey = GlobalKey(); - final textController = TextEditingController(); - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Add NZB by URL', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Add', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - if(formKey.currentState.validate()) { - flag = true; - Navigator.of(context).pop(); - } - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - Text( - 'Please enter the URL of the NZB file you want to add.', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - Form( - key: formKey, - child: TextFormField( - autofocus: true, - autocorrect: false, - controller: textController, - decoration: InputDecoration( - labelText: 'NZB URL', - labelStyle: TextStyle( - color: Colors.white54, - decoration: TextDecoration.none, - ), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Color(Constants.ACCENT_COLOR), - ), - ), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Color(Constants.ACCENT_COLOR), - ), - ), - ), - style: TextStyle( - color: Colors.white, - ), - cursorColor: Color(Constants.ACCENT_COLOR), - validator: (value) { - if(!value.startsWith('http://') && !value.startsWith('https://')) { - return 'Please enter a valid URL'; - } - return null; - }, - ), - ), - ], - ), - ), - ); - } - ); - return [flag, textController.text]; - } - - static Future> showRenameJobPrompt(BuildContext context, String originalName) async { - bool flag = false; - final formKey = GlobalKey(); - final textController = TextEditingController(); - textController.text = originalName; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Rename Job', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Rename', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - if(formKey.currentState.validate()) { - flag = true; - Navigator.of(context).pop(); - } - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - Text( - 'Please enter a new name for this job.', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - Form( - key: formKey, - child: TextFormField( - autofocus: true, - autocorrect: false, - controller: textController, - decoration: InputDecoration( - labelText: 'Name', - labelStyle: TextStyle( - color: Colors.white54, - decoration: TextDecoration.none, - ), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Color(Constants.ACCENT_COLOR), - ), - ), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Color(Constants.ACCENT_COLOR), - ), - ), - ), - style: TextStyle( - color: Colors.white, - ), - cursorColor: Color(Constants.ACCENT_COLOR), - validator: (value) { - if(value.length < 1) { - return 'Please enter a valid name'; - } - return null; - }, - ), - ), - ], - ), - ), - ); - } - ); - return [flag, textController.text]; - } - - static Future> showSetPasswordPrompt(BuildContext context) async { - bool flag = false; - final formKey = GlobalKey(); - final textController = TextEditingController(); - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Set Password', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Set', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - if(formKey.currentState.validate()) { - flag = true; - Navigator.of(context).pop(); - } - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - Text( - 'Please enter a password for this job.', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - Form( - key: formKey, - child: TextFormField( - autofocus: true, - autocorrect: false, - obscureText: true, - controller: textController, - decoration: InputDecoration( - labelText: 'Password', - labelStyle: TextStyle( - color: Colors.white54, - decoration: TextDecoration.none, - ), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Color(Constants.ACCENT_COLOR), - ), - ), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Color(Constants.ACCENT_COLOR), - ), - ), - ), - style: TextStyle( - color: Colors.white, - ), - cursorColor: Color(Constants.ACCENT_COLOR), - validator: (value) { - if(value.length < 1) { - return 'Please enter a valid password'; - } - return null; - }, - ), - ), - ], - ), - ), - ); - } - ); - return [flag, textController.text]; - } - - static Future> showSpeedPrompt(BuildContext context, int currentSpeed) async { - bool flag = false; - int limit = 0; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Speed Limit ($currentSpeed%)', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - ListTile( - leading: Icon( - Icons.timeline, - color: Colors.blue, - ), - title: Text( - '20%', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - limit = 20; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.timeline, - color: Color(Constants.ACCENT_COLOR), - ), - title: Text( - '40%', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - limit = 40; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.timeline, - color: Colors.orange, - ), - title: Text( - '60%', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - limit = 60; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.timeline, - color: Colors.red, - ), - title: Text( - '80%', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - limit = 80; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.timeline, - color: Colors.deepPurpleAccent, - ), - title: Text( - '100%', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - limit = 100; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.timeline, - color: Colors.blueGrey, - ), - title: Text( - 'Custom', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - limit = -1; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ], - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, limit]; - } - - static Future> showPauseForPrompt(BuildContext context) async { - bool flag = false; - int duration = 0; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Pause Queue For...', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - ListTile( - leading: Icon( - Icons.access_time, - color: Colors.blue, - ), - title: Text( - '5 Minutes', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - duration = 5; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.access_time, - color: Color(Constants.ACCENT_COLOR), - ), - title: Text( - '15 Minutes', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - duration = 15; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.access_time, - color: Colors.orange, - ), - title: Text( - '30 Minutes', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - duration = 30; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.access_time, - color: Colors.red, - ), - title: Text( - '1 Hour', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - duration = 60; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.access_time, - color: Colors.deepPurpleAccent, - ), - title: Text( - '3 Hours', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - duration = 180; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.access_time, - color: Colors.blueGrey, - ), - title: Text( - '6 Hours', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - duration = 360; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.access_time, - color: Colors.blue, - ), - title: Text( - 'Custom', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - duration = -1; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ], - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, duration]; - } - - static Future> showCustomPauseForPrompt(BuildContext context) async { - bool flag = false; - final formKey = GlobalKey(); - final textController = TextEditingController(); - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Custom Pause Duration', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Pause', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - if(formKey.currentState.validate()) { - flag = true; - Navigator.of(context).pop(); - } - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - Text( - 'Please enter how long in minutes you want to pause the queue for.', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - Form( - key: formKey, - child: TextFormField( - autofocus: true, - autocorrect: false, - controller: textController, - decoration: InputDecoration( - labelText: 'Pause Duration', - labelStyle: TextStyle( - color: Colors.white54, - decoration: TextDecoration.none, - ), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Color(Constants.ACCENT_COLOR), - ), - ), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Color(Constants.ACCENT_COLOR), - ), - ), - ), - style: TextStyle( - color: Colors.white, - ), - cursorColor: Color(Constants.ACCENT_COLOR), - validator: (value) { - int _value = int.tryParse(value); - if(_value == null || _value < 1) { - return 'Must be at least 1'; - } - return null; - }, - ), - ), - ], - ), - ), - ); - } - ); - return [flag, int.tryParse(textController.text)]; - } - - static Future> showChangePriorityPrompt(BuildContext context) async { - bool flag = false; - int priority = 0; - String name = ''; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Change Priority', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - ListTile( - leading: Icon( - Icons.low_priority, - color: Colors.blue, - ), - title: Text( - 'Category Default', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - priority = -100; - name = 'Category Default'; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.low_priority, - color: Color(Constants.ACCENT_COLOR), - ), - title: Text( - 'Force', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - priority = 2; - name = 'Force'; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.low_priority, - color: Colors.orange, - ), - title: Text( - 'High', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - priority = 1; - name = 'High'; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.low_priority, - color: Colors.red, - ), - title: Text( - 'Normal', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - priority = 0; - name = 'Normal'; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.low_priority, - color: Colors.deepPurpleAccent, - ), - title: Text( - 'Low', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - priority = -1; - name = 'Low'; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.low_priority, - color: Colors.blueGrey, - ), - title: Text( - 'Stop', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - priority = -4; - name = 'Stop'; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ], - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, priority, name]; - } - - static Future> showOnCompletePrompt(BuildContext context) async { - bool flag = false; - String action = ''; - String name = ''; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'On Complete Action', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - ListTile( - leading: Icon( - Icons.settings_power, - color: Colors.blue, - ), - title: Text( - 'Shutdown SABnzbd', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - name = 'Shutdown SABnzbd'; - action = 'shutdown_program'; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.settings_power, - color: Color(Constants.ACCENT_COLOR), - ), - title: Text( - 'Shutdown PC', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - name = 'Shutdown PC'; - action = 'shutdown_pc'; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.settings_power, - color: Colors.orange, - ), - title: Text( - 'Hibernate PC', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - name = 'Hibernate PC'; - action = 'hibernate_pc'; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.settings_power, - color: Colors.red, - ), - title: Text( - 'Standby PC', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - name = 'Standby PC'; - action = 'standby_pc'; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.settings_power, - color: Colors.deepPurpleAccent, - ), - title: Text( - 'Nothing', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - name = 'Nothing'; - action = 'none'; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ], - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, action, name]; - } - - static Future> showClearHistoryPrompt(BuildContext context) async { - bool flag = false; - bool delete = false; - String action = ''; - String name = ''; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Clear History', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - ListTile( - leading: Icon( - Icons.clear_all, - color: Colors.blue, - ), - title: Text( - 'Clear All', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - action = 'all'; - name = 'Clear All'; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.clear_all, - color: Color(Constants.ACCENT_COLOR), - ), - title: Text( - 'Clear Completed', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - action = 'completed'; - name = 'Clear Completed'; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.clear_all, - color: Colors.orange, - ), - title: Text( - 'Clear Failed', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - action = 'failed'; - name = 'Clear Failed'; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.clear_all, - color: Colors.red, - ), - title: Text( - 'Clear & Delete Failed', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - flag = true; - delete = true; - action = 'failed'; - name = 'Clear & Delete Failed'; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ], - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, action, delete, name]; - } - - static Future> showCustomSpeedPrompt(BuildContext context) async { - bool flag = false; - final formKey = GlobalKey(); - final textController = TextEditingController(); - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Custom Speed Limit', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Set', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - if(formKey.currentState.validate()) { - flag = true; - Navigator.of(context).pop(); - } - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - Text( - 'Please enter a percentage between 1 and 100.', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - Form( - key: formKey, - child: TextFormField( - autofocus: true, - autocorrect: false, - controller: textController, - decoration: InputDecoration( - labelText: 'Speed Limit', - labelStyle: TextStyle( - color: Colors.white54, - decoration: TextDecoration.none, - ), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Color(Constants.ACCENT_COLOR), - ), - ), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Color(Constants.ACCENT_COLOR), - ), - ), - ), - style: TextStyle( - color: Colors.white, - ), - cursorColor: Color(Constants.ACCENT_COLOR), - validator: (value) { - int _value = int.tryParse(value); - if(_value == null || _value < 1 || _value > 100) { - return 'Must be between 1 and 100'; - } - return null; - }, - ), - ), - ], - ), - ), - ); - } - ); - return [flag, int.tryParse(textController.text)]; - } - - static Future> showDeleteJobPrompt(BuildContext context) async { - bool flag = false; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Delete Job', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Delete', - style: TextStyle( - color: Colors.red, - ), - ), - onPressed: () { - flag = true; - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: Text( - 'Are you sure you want to delete this job?', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ), - ); - } - ); - return [flag]; - } - - static Future> showDeleteHistoryPrompt(BuildContext context) async { - bool flag = false; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Delete History', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Delete', - style: TextStyle( - color: Colors.red, - ), - ), - onPressed: () { - flag = true; - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: Text( - 'Are you sure you want to delete the history for this job?', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ), - ); - } - ); - return [flag]; - } -} diff --git a/lib/core/ui/dialogs/settings.dart b/lib/core/ui/dialogs/settings.dart deleted file mode 100644 index dd66530a..00000000 --- a/lib/core/ui/dialogs/settings.dart +++ /dev/null @@ -1,561 +0,0 @@ -import 'dart:io'; -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules.dart'; - -class LSDialogSettings { - LSDialogSettings._(); - - static Future deleteIndexer(BuildContext context) async { - //Returns - bool _flag = false; - //Dialog - await showDialog( - context: context, - builder: (context) => AlertDialog( - title: LSDialog.title(text: 'Delete Indexer'), - actions: [ - LSDialog.cancel(context), - LSDialog.button( - text: 'Delete', - onPressed: () { - _flag = true; - Navigator.of(context).pop(); - }, - textColor: Colors.red, - ), - ], - content: LSDialog.content( - children: [ - LSDialog.textContent( - text: 'Are you sure you want to delete this indexer?' - ), - ], - ), - ), - ); - return [_flag]; - } - - static Future showChangelog(BuildContext context, List changes) async { - const _headerStyle = TextStyle( - color: Color(Constants.ACCENT_COLOR), - fontWeight: FontWeight.bold, - ); - //Dialog - await showDialog( - context: context, - builder: (context) => AlertDialog( - title: LSDialog.title(text: 'Changelog'), - actions: [ - LSDialog.button( - text: 'Close', - textColor: LSColors.accent, - onPressed: () => Navigator.of(context).pop(), - ), - ], - content: LSDialog.content( - children: List.generate( - changes.length, - (index) => ListTile( - title: LSTitle( - text: '${changes[index]['version']}', - maxLines: 2, - ), - subtitle: Padding( - child: RichText( - text: TextSpan( - style: TextStyle(color: Colors.white70), - children: [ - TextSpan(text: 'New\n', style: _headerStyle), - LSDialog.textSpanContent(text: changes[index]['new'].length == 0 ? 'No Changes' : '- ${changes[index]['new'].join('\n- ')}'), - TextSpan(text: '\n\nFixes\n', style: _headerStyle), - LSDialog.textSpanContent(text: changes[index]['fixes'].length == 0 ? 'No Changes' : '- ${changes[index]['fixes'].join('\n- ')}'), - TextSpan(text: '\n\nTweaks\n', style: _headerStyle), - LSDialog.textSpanContent(text: changes[index]['tweaks'].length == 0 ? 'No Changes' : '- ${changes[index]['tweaks'].join('\n- ')}'), - ] - ), - ), - padding: EdgeInsets.symmetric(vertical: 10.0), - ), - ), - ).toList(), - ), - ), - ); - } - - static Future exportLogs(BuildContext context) async { - //Returns - bool _flag = false; - //Setter - void _setValues(bool flag) { - _flag = flag; - Navigator.of(context).pop(); - } - //Dialog - await showDialog( - context: context, - builder: (context) => AlertDialog( - title: LSDialog.title(text: 'Export Logs'), - actions: [ - LSDialog.cancel(context), - LSDialog.button( - text: 'Export', - onPressed: () => _setValues(true), - textColor: LSColors.accent, - ), - ], - content: LSDialog.content( - children: [ - LSDialog.textContent(text: 'Are you sure you want to export all recorded logs to the filesystem?\n'), - RichText( - textAlign: TextAlign.center, - text: TextSpan( - children: [ - if(Platform.isIOS || Platform.isAndroid) LSDialog.textSpanContent(text: 'The exported logs can be found in '), - if(Platform.isIOS) LSDialog.bolded(title: '/LunaSea/FLogs', fontSize: 16.0), - if(Platform.isAndroid) LSDialog.bolded(title: '/Android/data/app.lunasea.lunasea/files/FLogs', fontSize: 16.0), - LSDialog.textSpanContent(text: '.'), - ], - ), - ), - ], - ), - ), - ); - return [_flag]; - } - - static Future clearLogs(BuildContext context) async { - //Returns - bool _flag = false; - //Setter - void _setValues(bool flag) { - _flag = flag; - Navigator.of(context).pop(); - } - //Dialog - await showDialog( - context: context, - builder: (context) => AlertDialog( - title: LSDialog.title(text: 'Clear Logs'), - actions: [ - LSDialog.cancel(context), - LSDialog.button( - text: 'Clear', - onPressed: () => _setValues(true), - textColor: LSColors.red, - ), - ], - content: LSDialog.content( - children: [ - LSDialog.textContent(text: 'Are you sure you want to clear all recorded logs?\n\nLogs can be useful for bug reports and debugging.'), - ], - ), - ), - ); - return [_flag]; - } - - static Future toggleStrictTLS(BuildContext context) async { - //Returns - bool _flag = false; - //Setter - void _setValues(bool flag) { - _flag = flag; - Navigator.of(context).pop(); - } - //Dialog - await showDialog( - context: context, - builder: (context) => AlertDialog( - title: LSDialog.title(text: 'Disable Strict SSL/TLS Validation'), - actions: [ - LSDialog.cancel(context), - LSDialog.button( - text: 'Disable', - onPressed: () => _setValues(true), - textColor: LSColors.red, - ), - ], - content: RichText( - textAlign: TextAlign.center, - text: TextSpan( - children: [ - LSDialog.bolded( - title: 'Please do not modify this setting unless you know what you are doing.\n\n', - color: LSColors.red, - fontSize: 12.0, - ), - LSDialog.textSpanContent(text: 'Are you sure you want to disable strict SSL/TLS validation?\n\n'), - LSDialog.textSpanContent(text: 'Disabling strict SSL/TLS validation means that LunaSea will not validate the host machine\'s SSL certificate against a certificate authority.\n\n'), - LSDialog.textSpanContent(text: 'LunaSea will still connect to your host machine securely when using SSL/TLS whether strict SSL/TLS validation is enabled or disabled.\n\n'), - LSDialog.bolded( - title: 'Warning: Disabling strict SSL/TLS for an invalid or self-signed certificate will prevent a large amount of images from loading within LunaSea.', - color: LSColors.red, - fontSize: 12.0, - ), - ], - ), - ), - ), - ); - return [_flag]; - } - - static Future nzbgetBasicAuthentication(BuildContext context) async { - //Returns - bool _flag = false; - //Setter - void _setValues(bool flag) { - _flag = flag; - Navigator.of(context).pop(); - } - //Dialog - await showDialog( - context: context, - builder: (context) => AlertDialog( - title: LSDialog.title(text: 'Use Basic Authentication'), - actions: [ - LSDialog.cancel(context), - LSDialog.button( - text: 'Use', - onPressed: () => _setValues(true), - textColor: LSColors.red, - ), - ], - content: RichText( - textAlign: TextAlign.center, - text: TextSpan( - children: [ - LSDialog.bolded( - title: 'Please do not modify this setting unless you know what you are doing.\n\n', - color: LSColors.red, - fontSize: 12.0, - ), - LSDialog.textSpanContent(text: 'Are you sure you want to use basic authentication?\n\n'), - LSDialog.textSpanContent(text: 'Basic authentication will add your username and password as a header in the request instead of in the URL.\n\n'), - LSDialog.bolded( - title: 'Warning: This will allow you to have more complex passwords, but interfere with layered authentication methods.', - color: LSColors.red, - fontSize: 12.0, - ), - ], - ), - ), - ), - ); - return [_flag]; - } - - static Future addProfile(BuildContext context) async { - //Returns - TextEditingController _controller = TextEditingController(); - bool _flag = false; - //Setter - void _setValues(bool flag) { - _flag = flag; - Navigator.of(context).pop(); - } - //Dialog - await showDialog( - context: context, - builder: (context) => AlertDialog( - title: LSDialog.title(text: 'Add Profile'), - actions: [ - LSDialog.cancel(context), - LSDialog.button( - text: 'Save', - onPressed: () => _setValues(true), - textColor: LSColors.accent, - ), - ], - content: LSDialog.content( - children: [ - LSDialog.textInput( - controller: _controller, - onSubmitted: (_) => _setValues(true), - title: 'Profile Name', - ), - ], - ), - ), - ); - return [_flag, _controller.text]; - } - - static Future renameProfile(BuildContext context, List profiles) async { - //Returns - bool _flag = false; - String _profile = ''; - //Setter - void _setValues(bool flag, String profile) { - _flag = flag; - _profile = profile; - Navigator.of(context).pop(); - } - //Dialog - await showDialog( - context: context, - builder: (context) => AlertDialog( - title: LSDialog.title(text: 'Add Profile'), - actions: [ - LSDialog.cancel(context, textColor: LSColors.accent), - ], - content: LSDialog.content( - children: List.generate( - profiles.length, - (index) => ListTile( - leading: LSIcon( - icon: Icons.settings, - color: LSColors.list(index), - ), - title: Text( - profiles[index], - style: TextStyle(color: Colors.white), - ), - onTap: () => _setValues(true, profiles[index]), - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ), - ), - contentPadding: EdgeInsets.only(left: 24.0, top: 20.0, right: 24.0), - ), - ); - return [_flag, _profile]; - } - - static Future renameProfileSelected(BuildContext context) async { - //Returns - final _controller = TextEditingController(); - bool _flag = false; - //Setters - void _setValues(bool flag) { - _flag = flag; - Navigator.of(context).pop(); - } - await showDialog( - context: context, - builder: (BuildContext context) => AlertDialog( - title: LSDialog.title(text: 'Rename Profile'), - actions: [ - LSDialog.cancel(context), - LSDialog.button(text: 'Rename', onPressed: () => _setValues(true), textColor: LSColors.accent), - ], - content: LSDialog.content( - children: [ - LSDialog.textInput( - controller: _controller, - onSubmitted: (_) => _setValues(true), - title: 'New Profile Name', - ), - ], - ), - ), - ); - return [_flag, _controller.text]; - } - - static Future deleteProfile(BuildContext context, List profiles) async { - //Returns - bool _flag = false; - String _profile = ''; - //Setter - void _setValues(bool flag, String profile) { - _flag = flag; - _profile = profile; - Navigator.of(context).pop(); - } - //Dialog - await showDialog( - context: context, - builder: (context) => AlertDialog( - title: LSDialog.title(text: 'Delete Profile'), - actions: [ - LSDialog.cancel(context, textColor: LSColors.accent), - ], - content: LSDialog.content( - children: List.generate( - profiles.length, - (index) => ListTile( - leading: LSIcon( - icon: Icons.settings, - color: LSColors.list(index), - ), - title: Text( - profiles[index], - style: TextStyle(color: Colors.white), - ), - onTap: () => _setValues(true, profiles[index]), - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ), - ), - contentPadding: EdgeInsets.only(left: 24.0, top: 20.0, right: 24.0), - ), - ); - return [_flag, _profile]; - } - - static Future changeProfile(BuildContext context, List profiles) async { - //Returns - bool _flag = false; - String _profile = ''; - //Setter - void _setValues(bool flag, String profile) { - _flag = flag; - _profile = profile; - Navigator.of(context).pop(); - } - //Dialog - await showDialog( - context: context, - builder: (context) => AlertDialog( - title: LSDialog.title(text: 'Enabled Profile'), - actions: [ - LSDialog.cancel(context, textColor: LSColors.accent), - ], - content: LSDialog.content( - children: List.generate( - profiles.length, - (index) => ListTile( - leading: LSIcon( - icon: Icons.settings, - color: LSColors.list(index), - ), - title: Text( - profiles[index], - style: TextStyle(color: Colors.white), - ), - onTap: () => _setValues(true, profiles[index]), - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ), - ), - contentPadding: EdgeInsets.only(left: 24.0, top: 20.0, right: 24.0), - ), - ); - return [_flag, _profile]; - } - - static Future changeBrowser(BuildContext context) async { - //Returns - bool _flag = false; - LSBrowsers _browser; - //Setter - void _setValues(bool flag, LSBrowsers browser) { - _flag = flag; - _browser = browser; - Navigator.of(context).pop(); - } - await showDialog( - context: context, - builder: (context) => AlertDialog( - title: LSDialog.title(text: 'Open Links In...'), - actions: [ - LSDialog.cancel(context, textColor: LSColors.accent), - ], - content: LSDialog.content( - children: List.generate( - LSBrowsers.values.length, - (index) => ListTile( - leading: LSIcon( - icon: LSBrowsers.values[index].icon, - color: LSColors.list(index), - ), - title: Text( - LSBrowsers.values[index].name, - style: TextStyle(color: Colors.white), - ), - onTap: () => _setValues(true, LSBrowsers.values[index]), - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ), - ), - contentPadding: EdgeInsets.only(left: 24.0, top: 20.0, right: 24.0), - ), - ); - return [_flag, _browser]; - } - - static Future homeChangeStartingDay(BuildContext context) async { - //Returns - bool _flag = false; - CalendarStartingDay _startingDate; - //Setter - void _setValues(bool flag, CalendarStartingDay startingDate) { - _flag = flag; - _startingDate = startingDate; - Navigator.of(context).pop(); - } - await showDialog( - context: context, - builder: (context) => AlertDialog( - title: LSDialog.title(text: 'Starting Day of Week'), - actions: [ - LSDialog.cancel(context, textColor: LSColors.accent), - ], - content: LSDialog.content( - children: List.generate( - CalendarStartingDay.values.length, - (index) => ListTile( - leading: LSIcon( - icon: CustomIcons.calendar, - color: LSColors.list(index), - ), - title: Text( - CalendarStartingDay.values[index].name, - style: TextStyle(color: Colors.white), - ), - onTap: () => _setValues(true, CalendarStartingDay.values[index]), - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ), - ), - contentPadding: EdgeInsets.only(left: 24.0, top: 20.0, right: 24.0), - ), - ); - return [_flag, _startingDate]; - } - - static Future homeChangeStartingSize(BuildContext context) async { - //Returns - bool _flag = false; - CalendarStartingSize _startingSize; - //Setter - void _setValues(bool flag, CalendarStartingSize startingSize) { - _flag = flag; - _startingSize = startingSize; - Navigator.of(context).pop(); - } - await showDialog( - context: context, - builder: (context) => AlertDialog( - title: LSDialog.title(text: 'Starting Size'), - actions: [ - LSDialog.cancel(context, textColor: LSColors.accent), - ], - content: LSDialog.content( - children: List.generate( - CalendarStartingSize.values.length, - (index) => ListTile( - leading: LSIcon( - icon: CalendarStartingSize.values[index].icon, - color: LSColors.list(index), - ), - title: Text( - CalendarStartingSize.values[index].name, - style: TextStyle(color: Colors.white), - ), - onTap: () => _setValues(true, CalendarStartingSize.values[index]), - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ), - ), - contentPadding: EdgeInsets.only(left: 24.0, top: 20.0, right: 24.0), - ), - ); - return [_flag, _startingSize]; - } -} diff --git a/lib/core/ui/dialogs/sonarr.dart b/lib/core/ui/dialogs/sonarr.dart deleted file mode 100644 index 86dffbea..00000000 --- a/lib/core/ui/dialogs/sonarr.dart +++ /dev/null @@ -1,831 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:intl/intl.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class LSDialogSonarr { - LSDialogSonarr._(); - - static Future> showDownloadWarningPrompt(BuildContext context) async { - bool flag = false; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Download Release', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Download', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - flag = true; - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - Text( - 'Are you sure you want to download this release? It has been marked as a rejected release by Sonarr.', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - ], - ), - padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ), - ); - } - ); - return [flag]; - } - - static Future> showDeleteSeriesPrompt(BuildContext context) async { - bool flag = false; - bool files = false; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Remove Series', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Remove + Files', - style: TextStyle( - color: Colors.red, - ), - ), - onPressed: () { - flag = true; - files = true; - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Remove', - style: TextStyle( - color: Colors.red, - ), - ), - onPressed: () { - flag = true; - files = false; - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: Text( - 'Are you sure you want to remove the series from Sonarr?', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ), - ); - } - ); - return [flag, files]; - } - - static Future> showSearchMissingPrompt(BuildContext context) async { - bool flag = false; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Search All Missing', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Search', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - flag = true; - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: Text( - 'Are you sure you want to search for all missing episodes?', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ), - ); - } - ); - return [flag]; - } - - static Future> showSettingsPrompt(BuildContext context) async { - bool flag = false; - String value = ''; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Sonarr Settings', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - ListTile( - leading: Icon( - Icons.language, - color: Colors.blue, - ), - title: Text( - 'View Web GUI', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'web_gui'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.autorenew, - color: Color(Constants.ACCENT_COLOR), - ), - title: Text( - 'Update Library', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'update_library'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.rss_feed, - color: Colors.orange, - ), - title: Text( - 'Run RSS Sync', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'rss_sync'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.search, - color: Colors.red, - ), - title: Text( - 'Search All Missing', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'missing_search'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.save, - color: Colors.deepPurpleAccent, - ), - title: Text( - 'Backup Database', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'backup'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ], - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, value]; - } - - static Future> showEpisodeEditingPrompt(BuildContext context, String title, bool monitored, bool canDelete) async { - bool flag = false; - String value = ''; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - title, - maxLines: 1, - softWrap: false, - overflow: TextOverflow.fade, - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - ListTile( - leading: Icon( - monitored ? Icons.turned_in_not : Icons.turned_in, - color: Colors.blue, - ), - title: Text( - monitored ? 'Unmonitor Episode' : 'Monitor Episode', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'monitor_status'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.search, - color: Color(Constants.ACCENT_COLOR), - ), - title: Text( - 'Automatic Search', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'search_automatic'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.youtube_searched_for, - color: Colors.orange, - ), - title: Text( - 'Manual Search', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'search_manual'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - if(canDelete) ListTile( - leading: Icon( - Icons.delete, - color: Colors.red, - ), - title: Text( - 'Delete File', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'delete_file'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ], - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, value]; - } - - static Future> showEditSeriesPrompt(BuildContext context, SonarrCatalogueData entry) async { - bool flag = false; - String value = ''; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - entry.title, - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - ListTile( - leading: Icon( - Icons.edit, - color: Colors.blue, - ), - title: Text( - 'Edit Series', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'edit_series'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.refresh, - color: Color(Constants.ACCENT_COLOR), - ), - title: Text( - 'Refresh Series', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'refresh_series'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ListTile( - leading: Icon( - Icons.delete, - color: Colors.red, - ), - title: Text( - 'Remove Series', - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () { - value = 'remove_series'; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ), - ], - ), - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, value]; - } - - static Future> showEditRootFolderPrompt(BuildContext context, List folders) async { - bool flag = false; - SonarrRootFolder folder; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Root Folder', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: Container( - child: ListView.builder( - shrinkWrap: true, - itemCount: folders.length, - itemBuilder: (BuildContext context, int index) { - return ListTile( - title: Text( - folders[index].path, - style: TextStyle( - color: Colors.white, - ), - ), - subtitle: Text( - folders[index].freeSpace.lsBytes_BytesToString(), - style: TextStyle( - color: LSColors.accent, - fontWeight: FontWeight.bold, - ), - ), - leading: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - LSIcon( - icon: Icons.folder, - color: Constants.LIST_COLOR_ICONS[index%Constants.LIST_COLOR_ICONS.length], - ), - ], - ), - onTap: () { - folder = folders[index]; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ); - }, - ), - width: 400, - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 32.0, 0.0), - ); - } - ); - return [flag, folder]; - } - - static Future> showEditQualityProfilePrompt(BuildContext context, List qualities) async { - bool flag = false; - SonarrQualityProfile quality; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Quality Profile', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: Container( - child: ListView.builder( - shrinkWrap: true, - itemCount: qualities.length, - itemBuilder: (BuildContext context, int index) { - return ListTile( - title: Text( - qualities[index].name, - style: TextStyle( - color: Colors.white, - ), - ), - leading: Icon( - Icons.portrait, - color: Constants.LIST_COLOR_ICONS[index%Constants.LIST_COLOR_ICONS.length], - ), - onTap: () { - quality = qualities[index]; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ); - }, - ), - width: 400, - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 32.0, 0.0), - ); - } - ); - return [flag, quality]; - } - - static Future> showEditSeriesTypePrompt(BuildContext context) async { - bool flag = false; - SonarrSeriesType type; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Series Type', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: Container( - child: ListView.builder( - shrinkWrap: true, - itemCount: SonarrConstants.SERIES_TYPES.length, - itemBuilder: (BuildContext context, int index) { - return ListTile( - title: Text( - toBeginningOfSentenceCase(SonarrConstants.SERIES_TYPES[index].type), - style: TextStyle( - color: Colors.white, - ), - ), - leading: Icon( - Icons.tab, - color: Constants.LIST_COLOR_ICONS[index%Constants.LIST_COLOR_ICONS.length], - ), - onTap: () { - type = SonarrConstants.SERIES_TYPES[index]; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ); - }, - ), - width: 400, - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 32.0, 0.0), - ); - } - ); - return [flag, type]; - } - - static Future> showSearchSeasonPrompt(BuildContext context, int seasonNumber) async { - bool flag = false; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Episode Search', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Search', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - flag = true; - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: Text( - seasonNumber == 0 ? 'Search for all episodes in specials?' : 'Search for all episodes in season $seasonNumber?', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ), - ); - } - ); - return [flag]; - } - - static Future> showDeleteFilePrompt(BuildContext context) async { - bool flag = false; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Delete Episode File', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Delete', - style: TextStyle( - color: Colors.red, - ), - ), - onPressed: () { - flag = true; - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: Text( - 'Are you sure you want to delete this episode file?', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ), - ); - } - ); - return [flag]; - } -} diff --git a/lib/core/ui/dialogs/system.dart b/lib/core/ui/dialogs/system.dart deleted file mode 100644 index 88fca2e5..00000000 --- a/lib/core/ui/dialogs/system.dart +++ /dev/null @@ -1,435 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; - -class LSDialogSystem { - LSDialogSystem._(); - - static Future editText(BuildContext context, String title, {String prefill = '', bool showHostHint = false}) async { - //Returns - bool _flag = false; - TextEditingController _controller = TextEditingController()..text = prefill; - //Setter - void _setValues(bool flag) { - _flag = flag; - Navigator.of(context).pop(); - } - //Dialog - await showDialog( - context: context, - builder: (context) => AlertDialog( - title: LSDialog.title(text: title), - actions: [ - LSDialog.cancel(context), - LSDialog.button( - text: 'Save', - textColor: LSColors.accent, - onPressed: () => _setValues(true)), - ], - content: LSDialog.content( - children: [ - if(showHostHint) RichText( - text: TextSpan( - style: TextStyle(color: Colors.white70), - children: [ - TextSpan(text: '•\tThis is the URL in which you access the web GUI for the service\n'), - TextSpan(text: '•\tYou must include either '), - LSDialog.bolded(title: 'http://'), - TextSpan(text: ' or '), - LSDialog.bolded(title: 'https://\n'), - TextSpan(text: '•\tWhen not using a reverse proxy, please include the port: '), - LSDialog.bolded(title: 'url:port\n'), - TextSpan(text: '•\tTo add layered authentication (Nginx, Apache, etc), use the following format: '), - LSDialog.bolded(title: 'http(s)://user:pass@hostname\n'), - ], - ), - ), - LSDialog.textInput( - controller: _controller, - onSubmitted: (_) => _setValues(true), - title: title, - ), - ], - ), - ), - ); - return [_flag, _controller.text]; - } - - static Future textPreview(BuildContext context, String title, String text, {bool alignLeft = false}) async { - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: LSDialog.title(text: title), - actions: [ - LSDialog.button( - text: 'Close', - onPressed: () => Navigator.of(context).pop(), - textColor: LSColors.accent, - ), - ], - content: LSDialog.content( - children: [ - LSDialog.textContent( - text: text, - textAlign: alignLeft - ? TextAlign.left - : TextAlign.center, - ), - ], - ), - ); - } - ); - } - - static Future> deleteCatalogueWithFiles(BuildContext context, String title) async { - //Returns - bool _flag = false; - //Setter - void _setValues(bool flag) { - _flag = flag; - Navigator.of(context).pop(); - } - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: LSDialog.title(text: title), - actions: [ - LSDialog.cancel(context), - LSDialog.button( - text: 'Delete', - textColor: LSColors.red, - onPressed: () => _setValues(true), - ), - ], - content: LSDialog.content( - children: [ - LSDialog.textContent(text: 'Are you sure you want to delete all the files and folders for $title?'), - ], - ), - ); - } - ); - return [_flag]; - } - - static Future> showBackupConfigurationPrompt(BuildContext context) async { - bool flag = false; - final formKey = GlobalKey(); - final textController = TextEditingController(); - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Backup Configuration', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Backup', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - if(formKey.currentState.validate()) { - flag = true; - Navigator.of(context).pop(); - } - }, - ), - ], - content: LSDialog.content( - children: [ - LSDialog.textContent(text: 'Are you sure you want to backup your current configuration?\n'), - LSDialog.textContent(text: 'All backups are encrypted before being exported to the filesystem, please enter an encryption key for the backup.\n'), - RichText( - textAlign: TextAlign.center, - text: TextSpan( - children: [ - if(Platform.isIOS || Platform.isAndroid) LSDialog.textSpanContent(text: 'The backups can be found in '), - if(Platform.isIOS) LSDialog.bolded(title: '/LunaSea/configurations', fontSize: 16.0), - if(Platform.isAndroid) LSDialog.bolded(title: '/Android/data/app.lunasea.lunasea/files/configurations', fontSize: 16.0), - LSDialog.textSpanContent(text: '.'), - ], - ), - ), - Form( - key: formKey, - child: TextFormField( - autofocus: true, - autocorrect: false, - obscureText: true, - controller: textController, - decoration: InputDecoration( - labelText: 'Encryption Key', - labelStyle: TextStyle( - color: Colors.white54, - decoration: TextDecoration.none, - ), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Color(Constants.ACCENT_COLOR), - ), - ), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Color(Constants.ACCENT_COLOR), - ), - ), - ), - style: TextStyle( - color: Colors.white, - ), - cursorColor: Color(Constants.ACCENT_COLOR), - validator: (value) { - if(value.length < 8) { - return 'Minimum of 8 characters'; - } - return null; - }, - ), - ), - ], - ), - ); - } - ); - return [flag, textController.text]; - } - - static Future> showRestoreConfigurationPrompt(BuildContext context, List paths) async { - bool flag = false; - String path = ''; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Restore Configuration', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - content: Container( - child: ListView.builder( - shrinkWrap: true, - itemCount: paths.length, - itemBuilder: (BuildContext context, int index) { - return ListTile( - leading: Icon( - Icons.cloud_download, - color: Constants.LIST_COLOR_ICONS[index%Constants.LIST_COLOR_ICONS.length], - ), - title: Text( - paths[(paths.length-index-1)].substring( - paths[(paths.length-index-1)].indexOf('/configurations/')+16, - paths[(paths.length-index-1)].indexOf('.json') - ).replaceAll('%', '\n'), - style: TextStyle( - color: Colors.white, - ), - ), - onTap: () async { - path = paths[paths.length-index-1]; - flag = true; - Navigator.of(context).pop(); - }, - contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0), - ); - }, - ), - width: 400, - ), - contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ); - }, - ); - return [flag, path]; - } - - static Future> showClearConfigurationPrompt(BuildContext context) async { - bool flag = false; - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Reset LunaSea', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Reset', - style: TextStyle( - color: Colors.red, - ), - ), - onPressed: () { - flag = true; - Navigator.of(context).pop(); - }, - ), - ], - content: SingleChildScrollView( - child: Text( - 'Are you sure you want to reset LunaSea and clear your configuration?\n\nYou will be starting from a clean slate, please ensure you backup your current configuration first!', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0), - ), - ); - } - ); - return [flag]; - } - - static Future> showEncryptionKeyPrompt(BuildContext context) async { - bool flag = false; - final formKey = GlobalKey(); - final textController = TextEditingController(); - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - 'Decrypt Backup', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - actions: [ - FlatButton( - child: Text( - 'Cancel', - style: TextStyle( - color: Colors.white, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'Restore', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - ), - ), - onPressed: () { - if(formKey.currentState.validate()) { - flag = true; - Navigator.of(context).pop(); - } - }, - ), - ], - content: SingleChildScrollView( - child: ListBody( - children: [ - Text( - 'Please enter the encryption key used for this backup.', - style: TextStyle( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - Form( - key: formKey, - child: TextFormField( - autofocus: true, - autocorrect: false, - obscureText: true, - controller: textController, - decoration: InputDecoration( - labelText: 'Encryption Key', - labelStyle: TextStyle( - color: Colors.white54, - decoration: TextDecoration.none, - ), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Color(Constants.ACCENT_COLOR), - ), - ), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Color(Constants.ACCENT_COLOR), - ), - ), - ), - style: TextStyle( - color: Colors.white, - ), - cursorColor: Color(Constants.ACCENT_COLOR), - validator: (value) { - if(value.length < 8) { - return 'Minimum of 8 characters'; - } - return null; - }, - ), - ), - ], - ), - ), - ); - } - ); - return [flag, textController.text]; - } -} diff --git a/lib/core/ui/navigation.dart b/lib/core/ui/navigation.dart index 17cc9cbc..773de697 100644 --- a/lib/core/ui/navigation.dart +++ b/lib/core/ui/navigation.dart @@ -3,5 +3,3 @@ export './navigation/appbar_dropdown.dart'; export './navigation/appbar_tabs.dart'; export './navigation/drawer.dart'; export './navigation/bottom_navigation_bar.dart'; -export './navigation/sliver_appbar.dart'; -export './navigation/sliver_appbar_tabs.dart'; diff --git a/lib/core/ui/navigation/appbar.dart b/lib/core/ui/navigation/appbar.dart index 69ae719a..8443ea9c 100644 --- a/lib/core/ui/navigation/appbar.dart +++ b/lib/core/ui/navigation/appbar.dart @@ -5,7 +5,10 @@ Widget LSAppBar({ @required String title, List actions }) => AppBar( - title: Text(title), + title: Text( + title, + overflow: TextOverflow.fade, + ), centerTitle: false, elevation: 0, actions: actions, diff --git a/lib/core/ui/navigation/drawer.dart b/lib/core/ui/navigation/drawer.dart index d9522e5e..f72f61c2 100644 --- a/lib/core/ui/navigation/drawer.dart +++ b/lib/core/ui/navigation/drawer.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/wake_on_lan.dart'; class LSDrawer extends StatelessWidget { final String page; @@ -83,6 +84,7 @@ class LSDrawer extends StatelessWidget { title: 'Search', route: '/search', ), + if(ModuleFlags.WAKE_ON_LAN && profile.getWakeOnLAN()['enabled']) _buildWakeOnLAN(context: context), if(ModuleFlags.AUTOMATION && profile.anyAutomationEnabled) ExpansionTile( leading: Icon(CustomIcons.layers), title: Text('Automation'), @@ -91,9 +93,9 @@ class LSDrawer extends StatelessWidget { Database.currentProfileObject.enabledAutomationServices.length, (index) => _buildEntry( context: context, - route: Constants.SERVICE_MAP[Database.currentProfileObject.enabledAutomationServices[index]]['route'], - icon: Constants.SERVICE_MAP[Database.currentProfileObject.enabledAutomationServices[index]]['icon'], - title: Constants.SERVICE_MAP[Database.currentProfileObject.enabledAutomationServices[index]]['name'], + route: Constants.MODULE_MAP[Database.currentProfileObject.enabledAutomationServices[index]]['route'], + icon: Constants.MODULE_MAP[Database.currentProfileObject.enabledAutomationServices[index]]['icon'], + title: Constants.MODULE_MAP[Database.currentProfileObject.enabledAutomationServices[index]]['name'], padLeft: true, ), ), @@ -106,9 +108,9 @@ class LSDrawer extends StatelessWidget { Database.currentProfileObject.enabledClientServices.length, (index) => _buildEntry( context: context, - route: Constants.SERVICE_MAP[Database.currentProfileObject.enabledClientServices[index]]['route'], - icon: Constants.SERVICE_MAP[Database.currentProfileObject.enabledClientServices[index]]['icon'], - title: Constants.SERVICE_MAP[Database.currentProfileObject.enabledClientServices[index]]['name'], + route: Constants.MODULE_MAP[Database.currentProfileObject.enabledClientServices[index]]['route'], + icon: Constants.MODULE_MAP[Database.currentProfileObject.enabledClientServices[index]]['icon'], + title: Constants.MODULE_MAP[Database.currentProfileObject.enabledClientServices[index]]['name'], padLeft: true, ), ), @@ -149,4 +151,33 @@ class LSDrawer extends StatelessWidget { : EdgeInsets.fromLTRB(16.0, 0.0, 0.0, 0.0), ); } + + Widget _buildWakeOnLAN({ + @required BuildContext context, + }) { + return ListTile( + leading: LSIcon(icon: Constants.MODULE_MAP['wake_on_lan']['icon']), + title: Text( + Constants.MODULE_MAP['wake_on_lan']['name'], + style: TextStyle(color: Colors.white), + ), + onTap: () async { + WakeOnLANAPI _api = WakeOnLANAPI.from(Database.currentProfileObject); + await _api.wake() + .then((_) => LSSnackBar( + context: context, + title: 'Machine is Waking Up...', + message: 'Magic packet successfully sent', + type: SNACKBAR_TYPE.success, + )) + .catchError((_) => LSSnackBar( + context: context, + title: 'Failed to Wake Machine', + message: 'Magic packet failed to send', + type: SNACKBAR_TYPE.failure, + )); + }, + contentPadding: EdgeInsets.fromLTRB(16.0, 0.0, 0.0, 0.0), + ); + } } \ No newline at end of file diff --git a/lib/core/ui/navigation/sliver_appbar.dart b/lib/core/ui/navigation/sliver_appbar.dart deleted file mode 100644 index a6910d78..00000000 --- a/lib/core/ui/navigation/sliver_appbar.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_advanced_networkimage/transition.dart'; -import 'package:flutter_advanced_networkimage/provider.dart'; -import 'package:lunasea/core.dart'; - -class LSSliverAppBar extends StatelessWidget { - final ScrollController _controller = ScrollController(initialScrollOffset: _EXPANDED_HEIGHT - 80.0); - final String title; - final String backgroundURI; - final List actions; - final Widget body; - - LSSliverAppBar({ - @required this.title, - @required this.backgroundURI, - @required this.body, - this.actions, - }); - - static const _EXPANDED_HEIGHT = 200.0; - - @override - Widget build(BuildContext context) => NestedScrollView( - controller: _controller, - headerSliverBuilder: (context, innerBoxIsScrolled) => [ - SliverOverlapAbsorber( - sliver: SliverSafeArea( - top: false, - bottom: false, - sliver: SliverAppBar( - expandedHeight: _EXPANDED_HEIGHT, - pinned: true, - elevation: Constants.UI_ELEVATION, - title: Text( - title, - overflow: TextOverflow.fade, - softWrap: false, - maxLines: 1, - ), - centerTitle: false, - flexibleSpace: FlexibleSpaceBar( - collapseMode: CollapseMode.parallax, - background: TransitionToImage( - image: AdvancedNetworkImage( - backgroundURI, - useDiskCache: true, - fallbackAssetImage: LunaSeaDatabaseValue.THEME_AMOLED.data - ? 'assets/images/colors/black.png' - : 'assets/images/colors/secondary.png', - retryLimit: 1, - ), - fit: BoxFit.cover, - loadingWidget: Image.asset( - LunaSeaDatabaseValue.THEME_AMOLED.data - ? 'assets/images/colors/black.png' - : 'assets/images/colors/secondary.png', - ), - color: LSColors.secondary.withAlpha((255/1.5).floor()), - blendMode: BlendMode.darken, - ), - ), - actions: actions, - ), - ), - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - ], - body: body, - ); -} diff --git a/lib/core/ui/navigation/sliver_appbar_tabs.dart b/lib/core/ui/navigation/sliver_appbar_tabs.dart deleted file mode 100644 index 415cc88c..00000000 --- a/lib/core/ui/navigation/sliver_appbar_tabs.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_advanced_networkimage/transition.dart'; -import 'package:flutter_advanced_networkimage/provider.dart'; -import 'package:lunasea/core.dart'; - -class LSSliverAppBarTabs extends StatelessWidget { - final ScrollController _controller = ScrollController(initialScrollOffset: _EXPANDED_HEIGHT - 80.0); - final String title; - final String backgroundURI; - final List actions; - final Widget body; - final Widget bottom; - - LSSliverAppBarTabs({ - @required this.title, - @required this.backgroundURI, - this.actions, - @required this.body, - @required this.bottom, - }); - - static const _EXPANDED_HEIGHT = 200.0; - - @override - Widget build(BuildContext context) => NestedScrollView( - controller: _controller, - headerSliverBuilder: (context, innerBoxIsScrolled) => [ - SliverOverlapAbsorber( - sliver: SliverSafeArea( - top: false, - bottom: false, - sliver: SliverAppBar( - expandedHeight: _EXPANDED_HEIGHT, - pinned: true, - elevation: Constants.UI_ELEVATION, - title: Text( - title, - overflow: TextOverflow.fade, - softWrap: false, - maxLines: 1, - ), - centerTitle: false, - flexibleSpace: FlexibleSpaceBar( - collapseMode: CollapseMode.parallax, - background: TransitionToImage( - image: AdvancedNetworkImage( - backgroundURI, - useDiskCache: true, - fallbackAssetImage: LunaSeaDatabaseValue.THEME_AMOLED.data - ? 'assets/images/colors/black.png' - : 'assets/images/colors/secondary.png', - retryLimit: 1, - ), - fit: BoxFit.cover, - loadingWidget: Image.asset( - LunaSeaDatabaseValue.THEME_AMOLED.data - ? 'assets/images/colors/black.png' - : 'assets/images/colors/secondary.png', - ), - color: LSColors.secondary.withAlpha((255/1.5).floor()), - blendMode: BlendMode.darken, - ), - ), - actions: actions, - bottom: bottom, - ), - ), - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - ), - ], - body: body, - ); -} diff --git a/lib/core/ui/rive.dart b/lib/core/ui/rive.dart index 8b45e58d..4ceaa9a5 100644 --- a/lib/core/ui/rive.dart +++ b/lib/core/ui/rive.dart @@ -4,6 +4,5 @@ class RiveAnimations { static const CODER = { 'path': 'assets/rive/coding.flr', 'animation': 'coding', - }; } diff --git a/lib/core/ui/snackbar.dart b/lib/core/ui/snackbar.dart index a3c5bce9..91772f1b 100644 --- a/lib/core/ui/snackbar.dart +++ b/lib/core/ui/snackbar.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flushbar/flushbar.dart'; +import 'package:flash/flash.dart'; import 'package:lunasea/core.dart'; enum SNACKBAR_TYPE { @@ -14,7 +14,7 @@ const _LEFT_PADDING = 8.0; Future LSSnackBar({ @required BuildContext context, @required String message, - String title, + @required String title, Duration duration, SNACKBAR_TYPE type = SNACKBAR_TYPE.info, bool showButton = false, @@ -28,58 +28,58 @@ Future LSSnackBar({ case SNACKBAR_TYPE.success: color = LSColors.accent; icon = Icons.check_circle_outline; break; case SNACKBAR_TYPE.info: color = LSColors.blue; icon = Icons.info_outline; break; } - Flushbar( - titleText: Padding( - child: Text( - title, - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 18.0, - ), - ), - padding: EdgeInsets.only(left: _LEFT_PADDING), - ), - boxShadows: [BoxShadow(blurRadius: 6.0, spreadRadius: 4.0, color: Colors.black.withOpacity(0.10))], - messageText: Padding( - child: Text( - message, - style: TextStyle( - color: Colors.white70, - fontSize: 14.0, - ), - ), - padding: EdgeInsets.only(left: _LEFT_PADDING), - ), - //message: message, + showFlash( + context: context, duration: duration != null ? duration : showButton ? Duration(seconds: 4) : Duration(seconds: 2), - flushbarStyle: FlushbarStyle.FLOATING, - margin: EdgeInsets.all(8), - borderRadius: 8, - icon: Padding( - child: LSIconButton( - icon: icon, - color: color, - ), - padding: EdgeInsets.only(left: _LEFT_PADDING), - ), - mainButton: showButton - ? FlatButton( - child: Text( - buttonText.toUpperCase(), + builder: (context, controller) => Flash( + backgroundColor: LSColors.secondary, + controller: controller, + style: FlashStyle.floating, + boxShadows: [BoxShadow(blurRadius: 6.0, spreadRadius: 4.0, color: Colors.black.withOpacity(0.10))], + horizontalDismissDirection: HorizontalDismissDirection.horizontal, + margin: EdgeInsets.all(8.0), + borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), + child: FlashBar( + title: Text( + title, style: TextStyle( fontWeight: FontWeight.bold, + fontSize: 18.0, ), ), - textColor: LSColors.accent, - onPressed: buttonOnPressed, - ) - : null, - animationDuration: Duration(milliseconds: 375), - backgroundColor: Theme.of(context).primaryColor, - )..show(context); + message: Text( + message, + style: TextStyle( + color: Colors.white70, + ), + ), + icon: Padding( + child: LSIconButton( + icon: icon, + color: color, + ), + padding: EdgeInsets.only(left: _LEFT_PADDING), + ), + primaryAction: showButton + ? FlatButton( + child: Text( + buttonText.toUpperCase(), + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ), + textColor: LSColors.accent, + onPressed: () { + controller.dismiss(); + buttonOnPressed(); + } + ) + : null, + ), + ), + ); } diff --git a/lib/modules.dart b/lib/modules.dart index d24f75d2..43100b34 100644 --- a/lib/modules.dart +++ b/lib/modules.dart @@ -6,3 +6,4 @@ export './modules/sabnzbd.dart'; export './modules/search.dart'; export './modules/settings.dart'; export './modules/sonarr.dart'; +export './modules/wake_on_lan.dart'; diff --git a/lib/modules/home/core/api/api.dart b/lib/modules/home/core/api/api.dart index d5a5ded3..5d7f7d1f 100644 --- a/lib/modules/home/core/api/api.dart +++ b/lib/modules/home/core/api/api.dart @@ -60,14 +60,26 @@ class CalendarAPI extends API { try { String start = DateFormat('y-MM-dd').format(today.subtract(Duration(days: startOffset))); String end = DateFormat('y-MM-dd').format(today.add(Duration(days: endOffset))); - String uri = '${lidarr['host']}/api/v1/calendar?apikey=${lidarr['key']}&start=$start&end=$end'; - Dio _client = Dio(); + Map _headers = Map.from(lidarr['headers']); + Dio _client = Dio( + BaseOptions( + baseUrl: '${lidarr['host']}/api/v1/', + queryParameters: { + if(lidarr['key'] != '') 'apikey': lidarr['key'], + 'start': start, + 'end': end, + }, + headers: _headers, + followRedirects: true, + maxRedirects: 5, + ), + ); if(!lidarr['strict_tls']) { (_client.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) { client.badCertificateCallback = (X509Certificate cert, String host, int port) => true; }; } - Response response = await _client.get(uri); + Response response = await _client.get('calendar'); if(response.data.length > 0) { for(var entry in response.data) { DateTime date = DateTime.tryParse(entry['releaseDate'] ?? '')?.toLocal()?.lsDateTime_floor(); @@ -94,14 +106,26 @@ class CalendarAPI extends API { try { String start = DateFormat('y-MM-dd').format(today.subtract(Duration(days: startOffset))); String end = DateFormat('y-MM-dd').format(today.add(Duration(days: endOffset))); - String uri = '${radarr['host']}/api/calendar?apikey=${radarr['key']}&start=$start&end=$end'; - Dio _client = Dio(); + Map _headers = Map.from(radarr['headers']); + Dio _client = Dio( + BaseOptions( + baseUrl: '${radarr['host']}/api/', + queryParameters: { + if(radarr['key'] != '') 'apikey': radarr['key'], + 'start': start, + 'end': end, + }, + headers: _headers, + followRedirects: true, + maxRedirects: 5, + ), + ); if(!radarr['strict_tls']) { (_client.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) { client.badCertificateCallback = (X509Certificate cert, String host, int port) => true; }; } - Response response = await _client.get(uri); + Response response = await _client.get('calendar'); if(response.data.length > 0) { for(var entry in response.data) { DateTime date = DateTime.tryParse(entry['physicalRelease'] ?? '')?.toLocal()?.lsDateTime_floor(); @@ -129,14 +153,26 @@ class CalendarAPI extends API { try { String start = DateFormat('y-MM-dd').format(today.subtract(Duration(days: startOffset))); String end = DateFormat('y-MM-dd').format(today.add(Duration(days: endOffset))); - String uri = '${sonarr['host']}/api/calendar?apikey=${sonarr['key']}&start=$start&end=$end'; - Dio _client = Dio(); + Map _headers = Map.from(sonarr['headers']); + Dio _client = Dio( + BaseOptions( + baseUrl: '${sonarr['host']}/api/', + queryParameters: { + if(sonarr['key'] != '') 'apikey': sonarr['key'], + 'start': start, + 'end': end, + }, + headers: _headers, + followRedirects: true, + maxRedirects: 5, + ), + ); if(!sonarr['strict_tls']) { (_client.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) { client.badCertificateCallback = (X509Certificate cert, String host, int port) => true; }; } - Response response = await _client.get(uri); + Response response = await _client.get('calendar'); if(response.data.length > 0) { for(var entry in response.data) { DateTime date = DateTime.tryParse(entry['airDateUtc'] ?? '')?.toLocal()?.lsDateTime_floor(); diff --git a/lib/modules/home/routes/home.dart b/lib/modules/home/routes/home.dart index 1f7e31de..ae4bdcc9 100644 --- a/lib/modules/home/routes/home.dart +++ b/lib/modules/home/routes/home.dart @@ -22,23 +22,35 @@ class _State extends State { } @override - Widget build(BuildContext context) => ValueListenableBuilder( - valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]), - builder: (context, lunaBox, widget) { - return ValueListenableBuilder( - valueListenable: Database.profilesBox.listenable(keys: [Database.currentProfile]), - builder: (context, profileBox, widget) { - if(_profileState != Database.currentProfileObject.toString()) _refreshProfile(); - return Scaffold( - key: _scaffoldKey, - body: _body, - drawer: _drawer, - appBar: _appBar, - bottomNavigationBar: _bottomNavigationBar, - ); - } - ); + Widget build(BuildContext context) => WillPopScope( + onWillPop: () async { + if(_scaffoldKey.currentState.isDrawerOpen) { + //If the drawer is open, return true to close it + return true; + } else { + //If the drawer isn't open, open the drawer + _scaffoldKey.currentState.openDrawer(); + return false; + } }, + child: ValueListenableBuilder( + valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]), + builder: (context, lunaBox, widget) { + return ValueListenableBuilder( + valueListenable: Database.profilesBox.listenable(keys: [Database.currentProfile]), + builder: (context, profileBox, widget) { + if(_profileState != Database.currentProfileObject.toString()) _refreshProfile(); + return Scaffold( + key: _scaffoldKey, + body: _body, + drawer: _drawer, + appBar: _appBar, + bottomNavigationBar: _bottomNavigationBar, + ); + } + ); + }, + ), ); Widget get _drawer => LSDrawer(page: 'home'); diff --git a/lib/modules/home/routes/quick_access.dart b/lib/modules/home/routes/quick_access.dart index 91f52744..7facf5d7 100644 --- a/lib/modules/home/routes/quick_access.dart +++ b/lib/modules/home/routes/quick_access.dart @@ -30,21 +30,21 @@ class _State extends State with AutomaticKeepAliveClientMixin { itemBuilder: (context, index) { //Check if indexer search should be included at the top if(index == 0 && _hasIndexers) return HomeSummaryTile( - title: Constants.SERVICE_MAP['search']['name'], - subtitle: Constants.SERVICE_MAP['search']['desc'], - icon: Constants.SERVICE_MAP['search']['icon'], + title: Constants.MODULE_MAP['search']['name'], + subtitle: Constants.MODULE_MAP['search']['desc'], + icon: Constants.MODULE_MAP['search']['icon'], index: index, - route: Constants.SERVICE_MAP['search']['route'], + route: Constants.MODULE_MAP['search']['route'], ); if(index == _serviceCount-1) return HomeSummaryTile( - title: Constants.SERVICE_MAP['settings']['name'], - subtitle: Constants.SERVICE_MAP['settings']['desc'], - icon: Constants.SERVICE_MAP['settings']['icon'], + title: Constants.MODULE_MAP['settings']['name'], + subtitle: Constants.MODULE_MAP['settings']['desc'], + icon: Constants.MODULE_MAP['settings']['icon'], index: index, - route: Constants.SERVICE_MAP['settings']['route'], + route: Constants.MODULE_MAP['settings']['route'], justPush: true, ); - Map data = Constants.SERVICE_MAP[widget.profile.enabledServices[_hasIndexers ? index-1 : index]]; + Map data = Constants.MODULE_MAP[widget.profile.enabledServices[_hasIndexers ? index-1 : index]]; if(data != null) return HomeSummaryTile( title: data['name'], subtitle: data['desc'], diff --git a/lib/modules/home/widgets/calendar.dart b/lib/modules/home/widgets/calendar.dart index d3819e58..b9d6f9de 100644 --- a/lib/modules/home/widgets/calendar.dart +++ b/lib/modules/home/widgets/calendar.dart @@ -128,17 +128,29 @@ class _State extends State with TickerProviderStateMixin { ), ); - Widget _entry(dynamic event) => LSCardTile( - title: LSTitle(text: event.title), - subtitle: RichText( - text: event.subtitle, - overflow: TextOverflow.fade, - softWrap: false, - maxLines: 2, - ), - trailing: event.trailing, - onTap: () async => event.enterContent(context), - padContent: true, - decoration: LSCardBackground(uri: event.bannerURI), - ); + Widget _entry(dynamic event) { + Map headers; + switch(event.runtimeType) { + case CalendarLidarrData: headers = Database.currentProfileObject.getLidarr()['headers']; break; + case CalendarRadarrData: headers = Database.currentProfileObject.getRadarr()['headers']; break; + case CalendarSonarrData: headers = Database.currentProfileObject.getSonarr()['headers']; break; + default: headers = {}; break; + } + return LSCardTile( + title: LSTitle(text: event.title), + subtitle: RichText( + text: event.subtitle, + overflow: TextOverflow.fade, + softWrap: false, + maxLines: 2, + ), + trailing: event.trailing, + onTap: () async => event.enterContent(context), + padContent: true, + decoration: LSCardBackground( + uri: event.bannerURI, + headers: headers, + ), + ); + } } diff --git a/lib/modules/lidarr/core.dart b/lib/modules/lidarr/core.dart index 49296795..1a5be69e 100644 --- a/lib/modules/lidarr/core.dart +++ b/lib/modules/lidarr/core.dart @@ -1,4 +1,5 @@ export './core/api.dart'; export './core/constants.dart'; export './core/database.dart'; +export './core/dialogs.dart'; export './core/state.dart'; diff --git a/lib/modules/lidarr/core/api/api.dart b/lib/modules/lidarr/core/api/api.dart index 503ce16b..69c88352 100644 --- a/lib/modules/lidarr/core/api/api.dart +++ b/lib/modules/lidarr/core/api/api.dart @@ -11,12 +11,14 @@ class LidarrAPI extends API { LidarrAPI._internal(this._values, this._dio); factory LidarrAPI.from(ProfileHiveObject profile) { + Map _headers = Map.from(profile.getLidarr()['headers']); Dio _client = Dio( BaseOptions( baseUrl: '${profile.getLidarr()['host']}/api/v1/', queryParameters: { - 'apikey': profile.getLidarr()['key'], + if(profile.getLidarr()['key'] != '') 'apikey': profile.getLidarr()['key'], }, + headers: _headers, followRedirects: true, maxRedirects: 5, ), diff --git a/lib/modules/lidarr/core/constants.dart b/lib/modules/lidarr/core/constants.dart index 82b23406..898837f8 100644 --- a/lib/modules/lidarr/core/constants.dart +++ b/lib/modules/lidarr/core/constants.dart @@ -3,7 +3,9 @@ import 'package:lunasea/core.dart'; class LidarrConstants { LidarrConstants._(); - static const Map SERVICE_MAP = { + static const String MODULE_KEY = 'lidarr'; + + static const Map MODULE_MAP = { 'name': 'Lidarr', 'desc': 'Manage Music', 'icon': CustomIcons.music, diff --git a/lib/modules/lidarr/core/dialogs.dart b/lib/modules/lidarr/core/dialogs.dart new file mode 100644 index 00000000..cd893d27 --- /dev/null +++ b/lib/modules/lidarr/core/dialogs.dart @@ -0,0 +1,242 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/lidarr.dart'; + +class LidarrDialogs { + LidarrDialogs._(); + + static Future> editQualityProfile(BuildContext context, List qualities) async { + bool _flag = false; + LidarrQualityProfile _quality; + + void _setValues(bool flag, LidarrQualityProfile quality) { + _flag = flag; + _quality = quality; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Quality Profile', + content: List.generate( + qualities.length, + (index) => LSDialog.tile( + icon: Icons.portrait, + iconColor: LSColors.list(index), + text: qualities[index].name, + onTap: () => _setValues(true, qualities[index]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _quality]; + } + + static Future> editMetadataProfile(BuildContext context, List metadatas) async { + bool _flag = false; + LidarrMetadataProfile _metadata; + + void _setValues(bool flag, LidarrMetadataProfile metadata) { + _flag = flag; + _metadata = metadata; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Metadata Profile', + content: List.generate( + metadatas.length, + (index) => LSDialog.tile( + icon: Icons.portrait, + iconColor: LSColors.list(index), + text: metadatas[index].name, + onTap: () => _setValues(true, metadatas[index]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _metadata]; + } + + static Future> deleteArtist(BuildContext context) async { + bool _flag = false; + bool _files = false; + + void _setValues(bool flag, bool files) { + _flag = flag; + _files = files; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Remove Artist', + buttons: [ + LSDialog.button( + text: 'Remove + Files', + textColor: LSColors.red, + onPressed: () => _setValues(true, true), + ), + LSDialog.button( + text: 'Remove', + textColor: LSColors.red, + onPressed: () => _setValues(true, false), + ), + ], + content: [ + LSDialog.textContent(text: 'Are you sure you want to remove the artist from Lidarr?'), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag, _files]; + } + + static Future> downloadWarning(BuildContext context) async { + bool _flag = false; + + void _setValues(bool flag) { + _flag = flag; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Download Release', + buttons: [ + LSDialog.button( + text: 'Download', + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.textContent(text: 'Are you sure you want to download this release? It has been marked as a rejected release by Lidarr.'), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag]; + } + + static Future> searchAllMissing(BuildContext context) async { + bool _flag = false; + + void _setValues(bool flag) { + _flag = flag; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Search All Missing', + buttons: [ + LSDialog.button( + text: 'Search', + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.textContent(text: 'Are you sure you want to search for all missing albums?'), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag]; + } + + static Future> editArtist(BuildContext context, LidarrCatalogueData entry) async { + List> _options = [ + ['Edit Artist', Icons.edit, 'edit_artist'], + ['Refresh Artist', Icons.refresh, 'refresh_artist'], + ['Remove Artist', Icons.delete, 'remove_artist'], + ]; + bool _flag = false; + String _value = ''; + + void _setValues(bool flag, String value) { + _flag = flag; + _value = value; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: entry.title, + content: List.generate( + _options.length, + (index) => LSDialog.tile( + icon: _options[index][1], + iconColor: LSColors.list(index), + text: _options[index][0], + onTap: () => _setValues(true, _options[index][2]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _value]; + } + + static Future> editRootFolder(BuildContext context, List folders) async { + bool _flag = false; + LidarrRootFolder _folder; + + void _setValues(bool flag, LidarrRootFolder folder) { + _flag = flag; + _folder = folder; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Root Folder', + content: List.generate( + folders.length, + (index) => LSDialog.tile( + text: folders[index].path, + subtitle: LSDialog.richText( + children: [ + LSDialog.bolded(text: folders[index].freeSpace.lsBytes_BytesToString()), + ], + ), + icon: Icons.folder, + iconColor: LSColors.list(index), + onTap: () => _setValues(true, folders[index]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _folder]; + } + + static Future> globalSettings(BuildContext context) async { + List> _options = [ + ['View Web GUI', Icons.language, 'web_gui'], + ['Update Library', Icons.autorenew, 'update_library'], + ['Run RSS Sync', Icons.rss_feed, 'rss_sync'], + ['Search All Missing', Icons.search, 'missing_search'], + ['Backup Database', Icons.save, 'backup'], + ]; + bool _flag = false; + String _value = ''; + + void _setValues(bool flag, String value) { + _flag = flag; + _value = value; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Lidarr Settings', + content: List.generate( + _options.length, + (index) => LSDialog.tile( + text: _options[index][0], + icon: _options[index][1], + iconColor: LSColors.list(index), + onTap: () => _setValues(true, _options[index][2]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _value]; + } +} diff --git a/lib/modules/lidarr/core/state.dart b/lib/modules/lidarr/core/state.dart index 8d934bfb..715a15cb 100644 --- a/lib/modules/lidarr/core/state.dart +++ b/lib/modules/lidarr/core/state.dart @@ -56,4 +56,12 @@ class LidarrModel extends ChangeNotifier { _navigationIndex = navigationIndex; notifyListeners(); } + + int _artistNavigationIndex = 0; + int get artistNavigationIndex => _artistNavigationIndex; + set artistNavigationIndex(int artistNavigationIndex) { + assert(artistNavigationIndex != null); + _artistNavigationIndex = artistNavigationIndex; + notifyListeners(); + } } diff --git a/lib/modules/lidarr/routes/add_details.dart b/lib/modules/lidarr/routes/add_details.dart index 1e821282..3d3d1721 100644 --- a/lib/modules/lidarr/routes/add_details.dart +++ b/lib/modules/lidarr/routes/add_details.dart @@ -147,6 +147,7 @@ class _State extends State { : _arguments.data.overview, uri: _arguments.data.posterURI ?? '', fallbackImage: 'assets/images/lidarr/noartistposter.png', + headers: Database.currentProfileObject.getLidarr()['headers'], ), LSDivider(), ValueListenableBuilder( @@ -184,7 +185,7 @@ class _State extends State { subtitle: LSSubtitle(text: _rootfolder?.path ?? 'Unknown Root Folder'), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async { - List _values = await LSDialogLidarr.showEditRootFolderPrompt(context, _rootFolders); + List _values = await LidarrDialogs.editRootFolder(context, _rootFolders); if(_values[0]) box.put(LidarrDatabaseValue.ADD_ROOT_FOLDER.key, _values[1]); }, ); @@ -199,7 +200,7 @@ class _State extends State { subtitle: LSSubtitle(text: _profile?.name ?? 'Unknown Profile'), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async { - List _values = await LSDialogLidarr.showEditQualityProfilePrompt(context, _qualityProfiles); + List _values = await LidarrDialogs.editQualityProfile(context, _qualityProfiles); if(_values[0]) box.put(LidarrDatabaseValue.ADD_QUALITY_PROFILE.key, _values[1]); }, ); @@ -214,7 +215,7 @@ class _State extends State { subtitle: LSSubtitle(text: _profile?.name ?? 'Unknown Profile'), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async { - List _values = await LSDialogLidarr.showEditMetadataProfilePrompt(context, _metadataProfiles); + List _values = await LidarrDialogs.editMetadataProfile(context, _metadataProfiles); if(_values[0]) box.put(LidarrDatabaseValue.ADD_METADATA_PROFILE.key, _values[1]); }, ); diff --git a/lib/modules/lidarr/routes/details_artist.dart b/lib/modules/lidarr/routes/details_artist.dart index 2f75a58d..b57133cc 100644 --- a/lib/modules/lidarr/routes/details_artist.dart +++ b/lib/modules/lidarr/routes/details_artist.dart @@ -22,18 +22,16 @@ class LidarrDetailsArtist extends StatefulWidget { class _State extends State { final GlobalKey _scaffoldKey = GlobalKey(); + final _pageController = PageController(initialPage: 1); LidarrDetailsArtistArguments _arguments; bool _error = false; - final List _tabTitles = [ - 'Overview', - 'Albums', - ]; @override void initState() { super.initState(); SchedulerBinding.instance.addPostFrameCallback((_) { _arguments = ModalRoute.of(context).settings.arguments; + Provider.of(context, listen: false).artistNavigationIndex = 1; _fetch(); }); } @@ -51,46 +49,48 @@ class _State extends State { @override Widget build(BuildContext context) => Scaffold( key: _scaffoldKey, - appBar: _arguments == null || _arguments.data == null ? _appBar : null, + appBar: _appBar, + bottomNavigationBar: _arguments != null && _arguments.data != null + ? _bottomNavigationBar + : null, body: _arguments != null ? _arguments.data != null ? _body - : _error + : _error ? LSErrorMessage(onTapHandler: () => _fetch()) : LSLoading() : null, ); - Widget get _appBar => LSAppBar(title: 'Artist Details'); - - Widget get _body => DefaultTabController( - length: _tabTitles.length, - initialIndex: 1, - child: LSSliverAppBarTabs( - title: _arguments.data.title, - backgroundURI: _arguments.data.fanartURI(highRes: true), - bottom: TabBar( - tabs: [ - for(int i =0; i<_tabTitles.length; i++) Tab( - child: Text(_tabTitles[i]), - ), - ], - ), - body: TabBarView( - children: [ - LidarrDetailsOverview(data: _arguments.data), - LidarrDetailsAlbumList(artistID: _arguments.data.artistID), - ], - ), - actions: [ + Widget get _appBar => LSAppBar( + title: _arguments == null || _arguments.data == null + ? 'Artist Details' + : _arguments.data.title, + actions: _arguments == null || _arguments.data == null + ? null + : [ LidarrDetailsHideButton(), LidarrDetailsEditButton( data: _arguments.data, remove: (bool withData) => _removeCallback(withData), ), ], - ), ); + Widget get _bottomNavigationBar => LidarrArtistNavigationBar(pageController: _pageController); + + List get _tabs => [ + LidarrDetailsOverview(data: _arguments.data), + LidarrDetailsAlbumList(artistID: _arguments.data.artistID), + ]; + + Widget get _body => PageView( + controller: _pageController, + children: _tabs, + onPageChanged: _onPageChanged, + ); + + void _onPageChanged(int index) => Provider.of(context, listen: false).artistNavigationIndex = index; + Future _removeCallback(bool withData) async => Navigator.of(context).pop(['remove_artist', withData]); } diff --git a/lib/modules/lidarr/routes/edit_artist.dart b/lib/modules/lidarr/routes/edit_artist.dart index 26abf278..17301e1e 100644 --- a/lib/modules/lidarr/routes/edit_artist.dart +++ b/lib/modules/lidarr/routes/edit_artist.dart @@ -157,17 +157,17 @@ class _State extends State { ); Future _changePath() async { - List _values = await LSDialogSystem.editText(context, 'Artist Path', prefill: _path); + List _values = await GlobalDialogs.editText(context, 'Artist Path', prefill: _path); if(_values[0] && mounted) setState(() => _path = _values[1]); } Future _changeProfile() async { - List _values = await LSDialogLidarr.showEditQualityProfilePrompt(context, _qualityProfiles); + List _values = await LidarrDialogs.editQualityProfile(context, _qualityProfiles); if(_values[0] && mounted) setState(() => _qualityProfile = _values[1]); } Future _changeMetadata() async { - List _values = await LSDialogLidarr.showEditMetadataProfilePrompt(context, _metadataProfiles); + List _values = await LidarrDialogs.editMetadataProfile(context, _metadataProfiles); if(_values[0] && mounted) setState(() => _metadataProfile = _values[1]); } diff --git a/lib/modules/lidarr/routes/lidarr.dart b/lib/modules/lidarr/routes/lidarr.dart index 5f8a8330..20e1e5d0 100644 --- a/lib/modules/lidarr/routes/lidarr.dart +++ b/lib/modules/lidarr/routes/lidarr.dart @@ -28,18 +28,30 @@ class _State extends State { } @override - Widget build(BuildContext context) => ValueListenableBuilder( - valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]), - builder: (context, box, widget) { - if(_profileState != Database.currentProfileObject.toString()) _refreshProfile(); - return Scaffold( - key: _scaffoldKey, - body: _body, - drawer: _drawer, - appBar: _appBar, - bottomNavigationBar: _bottomNavigationBar, - ); + Widget build(BuildContext context) => WillPopScope( + onWillPop: () async { + if(_scaffoldKey.currentState.isDrawerOpen) { + //If the drawer is open, return true to close it + return true; + } else { + //If the drawer isn't open, open the drawer + _scaffoldKey.currentState.openDrawer(); + return false; + } }, + child: ValueListenableBuilder( + valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]), + builder: (context, box, widget) { + if(_profileState != Database.currentProfileObject.toString()) _refreshProfile(); + return Scaffold( + key: _scaffoldKey, + body: _body, + drawer: _drawer, + appBar: _appBar, + bottomNavigationBar: _bottomNavigationBar, + ); + }, + ), ); Widget get _drawer => LSDrawer(page: 'lidarr'); @@ -103,7 +115,7 @@ class _State extends State { } Future _handlePopup() async { - List values = await LSDialogLidarr.showSettingsPrompt(context); + List values = await LidarrDialogs.globalSettings(context); if(values[0]) switch(values[1]) { case 'web_gui': await _api.host?.toString()?.lsLinks_OpenLink(); break; case 'update_library': await _api.updateLibrary() @@ -119,7 +131,7 @@ class _State extends State { .catchError((_) => LSSnackBar(context: context, title: 'Failed to Backup Database', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure)); break; case 'missing_search': { - List values = await LSDialogLidarr.showSearchMissingPrompt(context); + List values = await LidarrDialogs.searchAllMissing(context); if(values[0]) await _api.searchAllMissing() .then((_) => LSSnackBar(context: context, title: 'Searching...', message: 'Search for all missing albums')) .catchError((_) => LSSnackBar(context: context, title: 'Failed to Search', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure)); diff --git a/lib/modules/lidarr/routes/search_details.dart b/lib/modules/lidarr/routes/search_details.dart index 6669e277..3fdb4417 100644 --- a/lib/modules/lidarr/routes/search_details.dart +++ b/lib/modules/lidarr/routes/search_details.dart @@ -63,7 +63,7 @@ class _State extends State { title: LSTitle(text: 'Release Title'), subtitle: LSSubtitle(text: _arguments.data.title), trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: () async => LSDialogSystem.textPreview(context, 'Release Title', _arguments.data.title), + onTap: () async => GlobalDialogs.textPreview(context, 'Release Title', _arguments.data.title), ), LSContainerRow( children: [ @@ -156,42 +156,22 @@ class _State extends State { Future _warningAction() async => _showWarnings(); Future _downloadAction() async { - if(_arguments.data.approved) { - await _startDownload() - .then((_) => LSSnackBar( - context: context, - title: 'Downloading...', - message: _arguments.data.title, - type: SNACKBAR_TYPE.success, - showButton: true, - buttonText: 'Back', - buttonOnPressed: () => Navigator.of(context).popUntil(ModalRoute.withName(Lidarr.ROUTE_NAME)), - )) - .catchError((_) => LSSnackBar( - context: context, - title: 'Failed to Start Downloading', - message: Constants.CHECK_LOGS_MESSAGE, - type: SNACKBAR_TYPE.failure, - )); - } else { - List values = await LSDialogLidarr.showDownloadWarningPrompt(context); - if(values[0]) await _startDownload() - .then((_) => LSSnackBar( - context: context, - title: 'Downloading...', - message: _arguments.data.title, - type: SNACKBAR_TYPE.success, - showButton: true, - buttonText: 'Back', - buttonOnPressed: () => Navigator.of(context).popUntil(ModalRoute.withName(Lidarr.ROUTE_NAME)), - )) - .catchError((_) => LSSnackBar( - context: context, - title: 'Failed to Start Downloading', - message: Constants.CHECK_LOGS_MESSAGE, - type: SNACKBAR_TYPE.failure, - )); - } + await _startDownload() + .then((_) => LSSnackBar( + context: context, + title: 'Downloading...', + message: _arguments.data.title, + type: SNACKBAR_TYPE.success, + showButton: true, + buttonText: 'Back', + buttonOnPressed: () => Navigator.of(context).popUntil(ModalRoute.withName(Lidarr.ROUTE_NAME)), + )) + .catchError((_) => LSSnackBar( + context: context, + title: 'Failed to Start Downloading', + message: Constants.CHECK_LOGS_MESSAGE, + type: SNACKBAR_TYPE.failure, + )); } Future _showWarnings() async { @@ -199,7 +179,7 @@ class _State extends State { for(var i=0; i<_arguments.data.rejections.length; i++) { reject += '${i+1}. ${_arguments.data.rejections[i]}\n'; } - await LSDialogSystem.textPreview(context, 'Rejection Reasons', reject.substring(0, reject.length-1)); + await GlobalDialogs.textPreview(context, 'Rejection Reasons', reject.substring(0, reject.length-1)); } Future _startDownload() async { diff --git a/lib/modules/lidarr/widgets.dart b/lib/modules/lidarr/widgets.dart index e773ace3..c98b87a1 100644 --- a/lib/modules/lidarr/widgets.dart +++ b/lib/modules/lidarr/widgets.dart @@ -14,3 +14,4 @@ export './widgets/search_result_tile.dart'; export './widgets/add_search_bar.dart'; export './widgets/add_search_result_tile.dart'; export './widgets/navigation_bar.dart'; +export './widgets/artist_navigation_bar.dart'; diff --git a/lib/modules/lidarr/widgets/artist_navigation_bar.dart b/lib/modules/lidarr/widgets/artist_navigation_bar.dart new file mode 100644 index 00000000..9af247d7 --- /dev/null +++ b/lib/modules/lidarr/widgets/artist_navigation_bar.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import '../../lidarr.dart'; + +class LidarrArtistNavigationBar extends StatefulWidget { + final PageController pageController; + + LidarrArtistNavigationBar({ + Key key, + @required this.pageController, + }): super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State { + static const List _navbarTitles = [ + 'Overview', + 'Albums', + ]; + + static const List _navbarIcons = [ + Icons.subject, + CustomIcons.music, + ]; + + @override + Widget build(BuildContext context) => Selector( + selector: (_, model) => model.artistNavigationIndex, + builder: (context, index, _) => LSBottomNavigationBar( + index: index, + icons: _navbarIcons, + titles: _navbarTitles, + onTap: (index) async => await _navOnTap(index), + ), + ); + + Future _navOnTap(int index) async { + await widget.pageController.animateToPage( + index, + duration: Duration(milliseconds: Constants.UI_NAVIGATION_SPEED), + curve: Curves.easeOutSine, + ).then((_) => Provider.of(context, listen: false).artistNavigationIndex = index); + } +} diff --git a/lib/modules/lidarr/widgets/catalogue_tile.dart b/lib/modules/lidarr/widgets/catalogue_tile.dart index 6e284d2f..99719df3 100644 --- a/lib/modules/lidarr/widgets/catalogue_tile.dart +++ b/lib/modules/lidarr/widgets/catalogue_tile.dart @@ -43,6 +43,7 @@ class _State extends State { padContent: true, decoration: LSCardBackground( uri: widget.data.bannerURI(), + headers: Database.currentProfileObject.getLidarr()['headers'], darken: !widget.data.monitored, ), onTap: () async => _enterArtist(), @@ -96,7 +97,7 @@ class _State extends State { } Future _handlePopup() async { - List values = await LSDialogLidarr.showEditArtistPrompt(context, widget.data); + List values = await LidarrDialogs.editArtist(context, widget.data); if(values[0]) switch(values[1]) { case 'refresh_artist': _refreshArtist(); break; case 'edit_artist': _enterEditArtist(); break; @@ -127,10 +128,10 @@ class _State extends State { Future _removeArtist() async { final _api = LidarrAPI.from(Database.currentProfileObject); - List values = await LSDialogLidarr.showDeleteArtistPrompt(context); + List values = await LidarrDialogs.deleteArtist(context); if(values[0]) { if(values[1]) { - values = await LSDialogSystem.deleteCatalogueWithFiles(context, widget.data.title); + values = await GlobalDialogs.deleteCatalogueWithFiles(context, widget.data.title); if(values[0]) { await _api.removeArtist(widget.data.artistID, deleteFiles: true) .then((_) { diff --git a/lib/modules/lidarr/widgets/details_album_list.dart b/lib/modules/lidarr/widgets/details_album_list.dart index 73899888..29458767 100644 --- a/lib/modules/lidarr/widgets/details_album_list.dart +++ b/lib/modules/lidarr/widgets/details_album_list.dart @@ -76,7 +76,7 @@ class _State extends State with AutomaticKeepAliveClient data: _filtered[index], refreshState: _refreshState, ), - padBottom: true, + padBottom: false, ); }, ); diff --git a/lib/modules/lidarr/widgets/details_album_tile.dart b/lib/modules/lidarr/widgets/details_album_tile.dart index bad725b4..ac231e34 100644 --- a/lib/modules/lidarr/widgets/details_album_tile.dart +++ b/lib/modules/lidarr/widgets/details_album_tile.dart @@ -30,6 +30,7 @@ class _State extends State { child: TransitionToImage( image: AdvancedNetworkImage( widget.data.albumCoverURI(), + header: Map.from(Database.currentProfileObject.getLidarr()['headers']), useDiskCache: true, fallbackAssetImage: 'assets/images/lidarr/noalbumart.png', retryLimit: 1, diff --git a/lib/modules/lidarr/widgets/details_edit_button.dart b/lib/modules/lidarr/widgets/details_edit_button.dart index 357278f7..b3812cdb 100644 --- a/lib/modules/lidarr/widgets/details_edit_button.dart +++ b/lib/modules/lidarr/widgets/details_edit_button.dart @@ -25,7 +25,7 @@ class _State extends State { ); Future _handlePopup(BuildContext context) async { - List values = await LSDialogLidarr.showEditArtistPrompt(context, widget.data); + List values = await LidarrDialogs.editArtist(context, widget.data); if(values[0]) switch(values[1]) { case 'refresh_artist': _refreshArtist(context); break; case 'edit_artist': _enterEditArtist(context); break; @@ -56,10 +56,10 @@ class _State extends State { Future _removeArtist(BuildContext context) async { final _api = LidarrAPI.from(Database.currentProfileObject); - List values = await LSDialogLidarr.showDeleteArtistPrompt(context); + List values = await LidarrDialogs.deleteArtist(context); if(values[0]) { if(values[1]) { - values = await LSDialogSystem.deleteCatalogueWithFiles(context, widget.data.title); + values = await GlobalDialogs.deleteCatalogueWithFiles(context, widget.data.title); if(values[0]) { await _api.removeArtist(widget.data.artistID, deleteFiles: true) .then((_) => widget.remove(true)) diff --git a/lib/modules/lidarr/widgets/details_overview.dart b/lib/modules/lidarr/widgets/details_overview.dart index 0aa97276..65dee8b6 100644 --- a/lib/modules/lidarr/widgets/details_overview.dart +++ b/lib/modules/lidarr/widgets/details_overview.dart @@ -30,11 +30,12 @@ class _State extends State with AutomaticKeepAliveClientM : widget?.data?.overview, uri: widget?.data?.posterURI() ?? '', fallbackImage: 'assets/images/lidarr/noartistposter.png', + headers: Database.currentProfileObject.getLidarr()['headers'], ), LSCardTile( title: LSTitle(text: 'Artist Path', centerText: true), subtitle: LSSubtitle(text: widget?.data?.path ?? 'Unknown', centerText: true), - onTap: () => LSDialogSystem.textPreview(context, 'Artist Path', widget?.data?.path ?? 'Unknown'), + onTap: () => GlobalDialogs.textPreview(context, 'Artist Path', widget?.data?.path ?? 'Unknown'), ), LSContainerRow( children: [ diff --git a/lib/modules/lidarr/widgets/missing_tile.dart b/lib/modules/lidarr/widgets/missing_tile.dart index 090543f6..cd2baaf3 100644 --- a/lib/modules/lidarr/widgets/missing_tile.dart +++ b/lib/modules/lidarr/widgets/missing_tile.dart @@ -50,7 +50,10 @@ class _State extends State { ), onTap: () async => _enterAlbum(), onLongPress: () async => _enterArtist(), - decoration: LSCardBackground(uri: widget.entry.bannerURI()), + decoration: LSCardBackground( + uri: widget.entry.bannerURI(), + headers: Database.currentProfileObject.getLidarr()['headers'], + ), padContent: true, ); diff --git a/lib/modules/lidarr/widgets/search_result_tile.dart b/lib/modules/lidarr/widgets/search_result_tile.dart index ad4abf5e..84dc569a 100644 --- a/lib/modules/lidarr/widgets/search_result_tile.dart +++ b/lib/modules/lidarr/widgets/search_result_tile.dart @@ -87,7 +87,7 @@ class LidarrSearchResultTile extends StatelessWidget { Future _trailingLongPressed(BuildContext context) async { if(!data.approved) { - List values = await LSDialogLidarr.showDownloadWarningPrompt(context); + List values = await LidarrDialogs.downloadWarning(context); if(values[0]) await _startDownload() .then((_) => LSSnackBar( context: context, @@ -112,7 +112,7 @@ class LidarrSearchResultTile extends StatelessWidget { for(var i=0; i _startDownload() async { diff --git a/lib/modules/nzbget.dart b/lib/modules/nzbget.dart index dbd50d62..d17af3c3 100644 --- a/lib/modules/nzbget.dart +++ b/lib/modules/nzbget.dart @@ -1,4 +1,3 @@ -export './nzbget/api.dart'; +export './nzbget/core.dart'; export './nzbget/routes.dart'; -export './nzbget/state.dart'; export './nzbget/widgets.dart'; diff --git a/lib/modules/nzbget/core.dart b/lib/modules/nzbget/core.dart new file mode 100644 index 00000000..1a5be69e --- /dev/null +++ b/lib/modules/nzbget/core.dart @@ -0,0 +1,5 @@ +export './core/api.dart'; +export './core/constants.dart'; +export './core/database.dart'; +export './core/dialogs.dart'; +export './core/state.dart'; diff --git a/lib/modules/nzbget/api.dart b/lib/modules/nzbget/core/api.dart similarity index 100% rename from lib/modules/nzbget/api.dart rename to lib/modules/nzbget/core/api.dart diff --git a/lib/modules/nzbget/api/api.dart b/lib/modules/nzbget/core/api/api.dart similarity index 99% rename from lib/modules/nzbget/api/api.dart rename to lib/modules/nzbget/core/api/api.dart index 634356ca..e8abdbec 100644 --- a/lib/modules/nzbget/api/api.dart +++ b/lib/modules/nzbget/core/api/api.dart @@ -4,7 +4,7 @@ import 'dart:math'; import 'package:dio/adapter.dart'; import 'package:dio/dio.dart'; import 'package:lunasea/core.dart'; -import '../../nzbget.dart'; +import 'package:lunasea/modules/nzbget.dart'; class NZBGetAPI extends API { final Map _values; @@ -13,7 +13,7 @@ class NZBGetAPI extends API { NZBGetAPI._internal(this._values, this._dio); factory NZBGetAPI.from(ProfileHiveObject profile) { String _baseURL = Uri.encodeFull(profile.getNZBGet()['host']); - Map _headers = {}; + Map _headers = Map.from(profile.getNZBGet()['headers']); if(profile.getNZBGet()['basic_auth']) { String _auth = base64.encode(utf8.encode('${profile.getNZBGet()['user']}:${profile.getNZBGet()['pass']}')); _headers['Authorization'] = 'Basic $_auth'; diff --git a/lib/modules/nzbget/api/data.dart b/lib/modules/nzbget/core/api/data.dart similarity index 100% rename from lib/modules/nzbget/api/data.dart rename to lib/modules/nzbget/core/api/data.dart diff --git a/lib/modules/nzbget/api/data/category.dart b/lib/modules/nzbget/core/api/data/category.dart similarity index 100% rename from lib/modules/nzbget/api/data/category.dart rename to lib/modules/nzbget/core/api/data/category.dart diff --git a/lib/modules/nzbget/api/data/history.dart b/lib/modules/nzbget/core/api/data/history.dart similarity index 100% rename from lib/modules/nzbget/api/data/history.dart rename to lib/modules/nzbget/core/api/data/history.dart diff --git a/lib/modules/nzbget/api/data/log.dart b/lib/modules/nzbget/core/api/data/log.dart similarity index 100% rename from lib/modules/nzbget/api/data/log.dart rename to lib/modules/nzbget/core/api/data/log.dart diff --git a/lib/modules/nzbget/api/data/priority.dart b/lib/modules/nzbget/core/api/data/priority.dart similarity index 100% rename from lib/modules/nzbget/api/data/priority.dart rename to lib/modules/nzbget/core/api/data/priority.dart diff --git a/lib/modules/nzbget/api/data/queue.dart b/lib/modules/nzbget/core/api/data/queue.dart similarity index 100% rename from lib/modules/nzbget/api/data/queue.dart rename to lib/modules/nzbget/core/api/data/queue.dart diff --git a/lib/modules/nzbget/api/data/sort.dart b/lib/modules/nzbget/core/api/data/sort.dart similarity index 100% rename from lib/modules/nzbget/api/data/sort.dart rename to lib/modules/nzbget/core/api/data/sort.dart diff --git a/lib/modules/nzbget/api/data/statistics.dart b/lib/modules/nzbget/core/api/data/statistics.dart similarity index 100% rename from lib/modules/nzbget/api/data/statistics.dart rename to lib/modules/nzbget/core/api/data/statistics.dart diff --git a/lib/modules/nzbget/api/data/status.dart b/lib/modules/nzbget/core/api/data/status.dart similarity index 100% rename from lib/modules/nzbget/api/data/status.dart rename to lib/modules/nzbget/core/api/data/status.dart diff --git a/lib/modules/nzbget/core/constants.dart b/lib/modules/nzbget/core/constants.dart new file mode 100644 index 00000000..3f212b64 --- /dev/null +++ b/lib/modules/nzbget/core/constants.dart @@ -0,0 +1,14 @@ +import 'package:lunasea/core.dart'; + +class NZBGetConstants { + NZBGetConstants._(); + + static const String MODULE_KEY = 'nzbget'; + + static const Map MODULE_MAP = { + 'name': 'NZBGet', + 'desc': 'Manage Usenet Downloads', + 'icon': CustomIcons.nzbget, + 'route': '/nzbget', + }; +} diff --git a/lib/modules/nzbget/core/database.dart b/lib/modules/nzbget/core/database.dart new file mode 100644 index 00000000..a7953dad --- /dev/null +++ b/lib/modules/nzbget/core/database.dart @@ -0,0 +1,3 @@ +class NZBGetDatabase { + NZBGetDatabase._(); +} diff --git a/lib/modules/nzbget/core/dialogs.dart b/lib/modules/nzbget/core/dialogs.dart new file mode 100644 index 00000000..7a558bb9 --- /dev/null +++ b/lib/modules/nzbget/core/dialogs.dart @@ -0,0 +1,568 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/nzbget.dart'; + +class NZBGetDialogs { + NZBGetDialogs._(); + + static Future> globalSettings(BuildContext context) async { + List> _options = [ + ['View Web GUI', Icons.language, 'web_gui'], + ['Add NZB', Icons.add, 'add_nzb'], + ['Sort Queue', Icons.sort, 'sort'], + ['Status & Statistics', Icons.info_outline, 'server_details'], + ]; + bool _flag = false; + String _value = ''; + + void _setValues(bool flag, String value) { + _flag = flag; + _value = value; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'NZBGet Settings', + content: List.generate( + _options.length, + (index) => LSDialog.tile( + text: _options[index][0], + icon: _options[index][1], + iconColor: LSColors.list(index), + onTap: () => _setValues(true, _options[index][2]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _value]; + } + + static Future> queueSettings(BuildContext context, String title, bool isPaused) async { + List> _options = [ + isPaused + ? ['Resume Job', Icons.play_arrow, 'status'] + : ['Pause Job', Icons.pause, 'status'], + ['Change Category', Icons.category, 'category'], + ['Change Priority', Icons.low_priority, 'priority'], + ['Set Password', Icons.vpn_key, 'password'], + ['Rename Job', Icons.text_format, 'rename'], + ['Delete Job', Icons.delete, 'delete'], + ]; + bool _flag = false; + String _value = ''; + + void _setValues(bool flag, String value) { + _flag = flag; + _value = value; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: title, + content: List.generate( + _options.length, + (index) => LSDialog.tile( + text: _options[index][0], + icon: _options[index][1], + iconColor: LSColors.list(index), + onTap: () => _setValues(true, _options[index][2]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _value]; + } + + static Future> deleteJob(BuildContext context) async { + bool _flag = false; + + void _setValues(bool flag) { + _flag = flag; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Delete Job', + buttons: [ + LSDialog.button( + text: 'Delete', + textColor: LSColors.red, + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.textContent(text: 'Are you sure you want to delete this job?'), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag]; + } + + static Future> renameJob(BuildContext context, String originalName) async { + bool _flag = false; + final _formKey = GlobalKey(); + final _textController = TextEditingController()..text = originalName; + + void _setValues(bool flag) { + if(_formKey.currentState.validate()) { + _flag = flag; + Navigator.of(context).pop(); + } + } + + await LSDialog.dialog( + context: context, + title: 'Rename Job', + buttons: [ + LSDialog.button( + text: 'Rename', + onPressed: () => _setValues(true), + ), + ], + content: [ + Form( + key: _formKey, + child: LSDialog.textFormInput( + controller: _textController, + title: 'Job Name', + onSubmitted: (_) => _setValues(true), + validator: (value) => value.length < 1 + ? 'Please enter a valid name' + : null, + ), + ), + ], + contentPadding: LSDialog.inputDialogContentPadding(), + ); + return [_flag, _textController.text]; + } + + static Future> changePriority(BuildContext context) async { + bool _flag = false; + NZBGetPriority _priority; + + void _setValues(bool flag, NZBGetPriority priority) { + _flag = flag; + _priority = priority; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Change Priority', + content: List.generate( + NZBGetPriority.values.length, + (index) => LSDialog.tile( + text: NZBGetPriority.values[index].name, + icon: Icons.low_priority, + iconColor: LSColors.list(index), + onTap: () => _setValues(true, NZBGetPriority.values[index]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _priority]; + } + + static Future> changeCategory(BuildContext context, List categories) async { + bool _flag = false; + NZBGetCategoryData _category; + + void _setValues(bool flag, NZBGetCategoryData category) { + _flag = flag; + _category = category; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Change Category', + content: List.generate( + categories.length, + (index) => LSDialog.tile( + text: categories[index].name, + icon: Icons.category, + iconColor: LSColors.list(index), + onTap: () => _setValues(true, categories[index]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _category]; + } + + static Future> setPassword(BuildContext context) async { + bool _flag = false; + final _formKey = GlobalKey(); + final _textController = TextEditingController(); + + void _setValues(bool flag) { + if(_formKey.currentState.validate()) { + _flag = flag; + Navigator.of(context).pop(); + } + } + + await LSDialog.dialog( + context: context, + title: 'Set Password', + buttons: [ + LSDialog.button( + text: 'Set', + onPressed: () => _setValues(true), + ), + ], + content: [ + Form( + key: _formKey, + child: LSDialog.textFormInput( + controller: _textController, + title: 'Job Password', + onSubmitted: (_) => _setValues(true), + validator: (value) => value.length < 1 + ? 'Please enter a valid password' + : null, + obscureText: true, + ), + ), + ], + contentPadding: LSDialog.inputDialogContentPadding(), + ); + return [_flag, _textController.text]; + } + + static Future> deleteHistory(BuildContext context) async { + bool _flag = false; + bool _hide = false; + + void _setValues(bool flag, bool hide) { + _flag = flag; + _hide = hide; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Remove History', + buttons: [ + LSDialog.button( + text: 'Hide', + onPressed: () => _setValues(true, true), + ), + LSDialog.button( + text: 'Delete', + textColor: LSColors.red, + onPressed: () => _setValues(true, false), + ), + ], + content: [ + LSDialog.textContent(text: 'Are you sure you want to remove the history for this job?'), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag, _hide]; + } + + static Future> sortQueue(BuildContext context) async { + bool _flag = false; + NZBGetSort _sort; + + void _setValues(bool flag, NZBGetSort sort) { + _flag = flag; + _sort = sort; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Sort Queue', + content: List.generate( + NZBGetSort.values.length, + (index) => LSDialog.tile( + text: NZBGetSort.values[index].name, + icon: NZBGetSort.values[index].icon, + iconColor: LSColors.list(index), + onTap: () => _setValues(true, NZBGetSort.values[index]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _sort]; + } + + static Future> addNZB(BuildContext context) async { + List> _options = [ + ['Add by URL', Icons.link, 'link'], + ['Add by File', Icons.sd_card, 'file'], + ]; + bool _flag = false; + String _type = ''; + + void _setValues(bool flag, String type) { + _flag = flag; + _type = type; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Add NZB', + content: List.generate( + _options.length, + (index) => LSDialog.tile( + text: _options[index][0], + icon: _options[index][1], + iconColor: LSColors.list(index), + onTap: () => _setValues(true, _options[index][2]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _type]; + } + + static Future> addNZBUrl(BuildContext context) async { + bool _flag = false; + final _formKey = GlobalKey(); + final _textController = TextEditingController(); + + void _setValues(bool flag) { + if(_formKey.currentState.validate()) { + _flag = flag; + Navigator.of(context).pop(); + } + } + + await LSDialog.dialog( + context: context, + title: 'Add NZB by URL', + buttons: [ + LSDialog.button( + text: 'Add', + onPressed: () => _setValues(true), + ), + ], + content: [ + Form( + key: _formKey, + child: LSDialog.textFormInput( + controller: _textController, + title: 'NZB URL', + keyboardType: TextInputType.url, + onSubmitted: (_) => _setValues(true), + validator: (value) => !value.startsWith('http://') && !value.startsWith('https://') + ? 'Please enter a valid URL' + : null, + ), + ), + ], + contentPadding: LSDialog.inputDialogContentPadding(), + ); + return [_flag, _textController.text]; + } + + static Future> speedLimit(BuildContext context, String currentSpeed) async { + List> _options = [ + ['Unlimited', Icons.timeline, 0], + ['125 MB/s', Icons.timeline, 128000], + ['100 MB/s', Icons.timeline, 102400], + ['75 MB/s', Icons.timeline, 76800], + ['50 MB/s', Icons.timeline, 51200], + ['25 MB/s', Icons.timeline, 25600], + ['10 MB/s', Icons.timeline, 10240], + ['Custom...', Icons.timeline, -1], + ]; + bool _flag = false; + int _limit = 0; + + void _setValues(bool flag, int limit) { + _flag = flag; + _limit = limit; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: currentSpeed == 'Unlimited' ? 'Speed Limit (Unlimited)' : 'Speed Limit ($currentSpeed/s)', + content: List.generate( + _options.length, + (index) => LSDialog.tile( + text: _options[index][0], + icon: _options[index][1], + iconColor: LSColors.list(index), + onTap: () => _setValues(true, _options[index][2]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _limit]; + } + + static Future> customSpeedLimit(BuildContext context) async { + bool _flag = false; + final _formKey = GlobalKey(); + final _textController = TextEditingController(); + + void _setValues(bool flag) { + if(_formKey.currentState.validate()) { + _flag = flag; + Navigator.of(context).pop(); + } + } + + await LSDialog.dialog( + context: context, + title: 'Custom Speed Limit', + buttons: [ + LSDialog.button( + text: 'Set', + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.textContent(text: 'Please enter a speed limit in KB/s.'), + Form( + key: _formKey, + child: LSDialog.textFormInput( + controller: _textController, + title: 'Speed Limit', + onSubmitted: (_) => _setValues(true), + keyboardType: TextInputType.number, + validator: (value) { + int _value = int.tryParse(value); + if(_value == null || _value < 1) { + return 'Must be a number greater than 1'; + } + return null; + }, + ), + ), + ], + contentPadding: LSDialog.inputTextDialogContentPadding(), + ); + return [_flag, int.tryParse(_textController.text)]; + } + + static Future> historySettings(BuildContext context, String title) async { + List> _options = [ + ['Retry Job', Icons.autorenew, 'retry'], + ['Hide History', Icons.visibility_off, 'hide'], + ['Delete History', Icons.delete, 'delete'], + ]; + bool _flag = false; + String _value = ''; + + void _setValues(bool flag, String value) { + _flag = flag; + _value = value; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: title, + content: List.generate( + _options.length, + (index) => LSDialog.tile( + text: _options[index][0], + icon: _options[index][1], + iconColor: LSColors.list(index), + onTap: () => _setValues(true, _options[index][2]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _value]; + } + + static Future> pauseFor(BuildContext context) async { + List> _options = [ + ['5 Minutes', Icons.access_time, 5], + ['15 Minutes', Icons.access_time, 15], + ['30 Minutes', Icons.access_time, 30], + ['1 Hour', Icons.access_time, 60], + ['3 Hours', Icons.access_time, 180], + ['6 Hours', Icons.access_time, 360], + ['Custom...', Icons.access_time, -1], + ]; + bool _flag = false; + int _duration = 0; + + void _setValues(bool flag, int duration) { + _flag = flag; + _duration = duration; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Pause Queue For...', + content: List.generate( + _options.length, + (index) => LSDialog.tile( + text: _options[index][0], + icon: _options[index][1], + iconColor: LSColors.list(index), + onTap: () => _setValues(true, _options[index][2]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _duration]; + } + + static Future> customPauseFor(BuildContext context) async { + bool _flag = false; + final _formKey = GlobalKey(); + final _textController = TextEditingController(); + + void _setValues(bool flag) { + if(_formKey.currentState.validate()) { + _flag = flag; + Navigator.of(context).pop(); + } + } + + await LSDialog.dialog( + context: context, + title: 'Custom Pause Duration', + buttons: [ + LSDialog.button( + text: 'Pause', + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.richText( + children: [ + LSDialog.textSpanContent(text: 'Please enter how long in '), + LSDialog.bolded(text: 'minutes'), + LSDialog.textSpanContent(text: ' you want to pause the queue for.'), + ], + alignment: TextAlign.center, + ), + Form( + key: _formKey, + child: LSDialog.textFormInput( + controller: _textController, + title: 'Pause Duration in Minutes', + keyboardType: TextInputType.number, + onSubmitted: (_) => _setValues(true), + validator: (value) { + int _value = int.tryParse(value); + if(_value == null || _value < 1) { + return 'Must be at least 1'; + } + return null; + } + ), + ), + ], + contentPadding: LSDialog.inputTextDialogContentPadding(), + ); + return [_flag, int.tryParse(_textController.text)]; + } +} diff --git a/lib/modules/nzbget/state.dart b/lib/modules/nzbget/core/state.dart similarity index 100% rename from lib/modules/nzbget/state.dart rename to lib/modules/nzbget/core/state.dart diff --git a/lib/modules/nzbget/routes/history_details.dart b/lib/modules/nzbget/routes/history_details.dart index f1d11071..4d35bc74 100644 --- a/lib/modules/nzbget/routes/history_details.dart +++ b/lib/modules/nzbget/routes/history_details.dart @@ -113,7 +113,7 @@ class _State extends State { title: LSTitle(text: 'Storage Location'), subtitle: LSSubtitle(text: _arguments.data.storageLocation), trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: () async => LSDialogSystem.textPreview(context, 'Storage Location', _arguments.data.storageLocation), + onTap: () async => GlobalDialogs.textPreview(context, 'Storage Location', _arguments.data.storageLocation), ), LSDivider(), LSButton( @@ -126,7 +126,7 @@ class _State extends State { ); Future _delete() async { - List values = await LSDialogNZBGet.showDeleteHistoryPrompt(context); + List values = await NZBGetDialogs.deleteHistory(context); if(values[0]) await NZBGetAPI.from(Database.currentProfileObject).deleteHistoryEntry( _arguments.data.id, hide: values[1], diff --git a/lib/modules/nzbget/routes/nzbget.dart b/lib/modules/nzbget/routes/nzbget.dart index f89e4fba..133bccf2 100644 --- a/lib/modules/nzbget/routes/nzbget.dart +++ b/lib/modules/nzbget/routes/nzbget.dart @@ -29,18 +29,30 @@ class _State extends State { } @override - Widget build(BuildContext context) => ValueListenableBuilder( - valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]), - builder: (context, box, widget) { - if(_profileState != Database.currentProfileObject.toString()) _refreshProfile(); - return Scaffold( - key: _scaffoldKey, - body: _body, - drawer: _drawer, - appBar: _appBar, - bottomNavigationBar: _bottomNavigationBar, - ); + Widget build(BuildContext context) => WillPopScope( + onWillPop: () async { + if(_scaffoldKey.currentState.isDrawerOpen) { + //If the drawer is open, return true to close it + return true; + } else { + //If the drawer isn't open, open the drawer + _scaffoldKey.currentState.openDrawer(); + return false; + } }, + child: ValueListenableBuilder( + valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]), + builder: (context, box, widget) { + if(_profileState != Database.currentProfileObject.toString()) _refreshProfile(); + return Scaffold( + key: _scaffoldKey, + body: _body, + drawer: _drawer, + appBar: _appBar, + bottomNavigationBar: _bottomNavigationBar, + ); + }, + ), ); Widget get _drawer => LSDrawer(page: 'nzbget'); @@ -82,7 +94,7 @@ class _State extends State { ); Future _handlePopup() async { - List values = await LSDialogNZBGet.showSettingsPrompt(context); + List values = await NZBGetDialogs.globalSettings(context); if(values[0]) switch(values[1]) { case 'web_gui': _api.host.lsLinks_OpenLink(); break; case 'add_nzb': _addNZB(); break; @@ -93,7 +105,7 @@ class _State extends State { } Future _addNZB() async { - List values = await LSDialogNZBGet.showAddNZBPrompt(context); + List values = await NZBGetDialogs.addNZB(context); if(values[0]) switch(values[1]) { case 'link': _addByURL(); break; case 'file': _addByFile(); break; @@ -102,7 +114,7 @@ class _State extends State { } Future _addByURL() async { - List values = await LSDialogNZBGet.showaddURLPrompt(context); + List values = await NZBGetDialogs.addNZBUrl(context); if(values[0]) await _api.uploadURL(values[1]) .then((_) => LSSnackBar( context: context, @@ -159,7 +171,7 @@ class _State extends State { } Future _sort() async { - List values = await LSDialogNZBGet.showSortPrompt(context); + List values = await NZBGetDialogs.sortQueue(context); if(values[0]) await _api.sortQueue(values[1]) .then((_) { _refreshKeys[0]?.currentState?.show(); diff --git a/lib/modules/nzbget/widgets/app_bar_stats.dart b/lib/modules/nzbget/widgets/app_bar_stats.dart index 40a2c5c3..df160ae2 100644 --- a/lib/modules/nzbget/widgets/app_bar_stats.dart +++ b/lib/modules/nzbget/widgets/app_bar_stats.dart @@ -50,10 +50,10 @@ class NZBGetAppBarStats extends StatelessWidget { : speed; Future _onTap(BuildContext context, String speed) async { - List values = await LSDialogNZBGet.showSpeedPrompt(context, speed); + List values = await NZBGetDialogs.speedLimit(context, speed); if(values[0]) switch(values[1]) { case -1: { - values = await LSDialogNZBGet.showCustomSpeedPrompt(context); + values = await NZBGetDialogs.customSpeedLimit(context); if(values[0]) NZBGetAPI.from(Database.currentProfileObject).setSpeedLimit(values[1]) .then((_) => LSSnackBar( context: context, diff --git a/lib/modules/nzbget/widgets/history_tile.dart b/lib/modules/nzbget/widgets/history_tile.dart index 5325608d..f1804a68 100644 --- a/lib/modules/nzbget/widgets/history_tile.dart +++ b/lib/modules/nzbget/widgets/history_tile.dart @@ -56,7 +56,7 @@ class NZBGetHistoryTile extends StatelessWidget { } Future _handlePopup(BuildContext context) async { - List values = await LSDialogNZBGet.showHistorySettingsPrompt(context, data.name); + List values = await NZBGetDialogs.historySettings(context, data.name); if(values[0]) switch(values[1]) { case 'retry': { await NZBGetAPI.from(Database.currentProfileObject).retryHistoryEntry(data.id) diff --git a/lib/modules/nzbget/widgets/log_tile.dart b/lib/modules/nzbget/widgets/log_tile.dart index 3111261c..63aaa3d9 100644 --- a/lib/modules/nzbget/widgets/log_tile.dart +++ b/lib/modules/nzbget/widgets/log_tile.dart @@ -14,6 +14,6 @@ class NZBGetLogTile extends StatelessWidget { title: LSTitle(text: data.text), subtitle: LSSubtitle(text: data.timestamp), trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: () async => LSDialogSystem.textPreview(context, 'Log Entry', data.text), + onTap: () async => GlobalDialogs.textPreview(context, 'Log Entry', data.text), ); } diff --git a/lib/modules/nzbget/widgets/queue_fab.dart b/lib/modules/nzbget/widgets/queue_fab.dart index 03b710e4..d1b518d2 100644 --- a/lib/modules/nzbget/widgets/queue_fab.dart +++ b/lib/modules/nzbget/widgets/queue_fab.dart @@ -27,10 +27,10 @@ class NZBGetQueueFAB extends StatelessWidget { } Future _toggleFor(BuildContext context) async { - List values = await LSDialogNZBGet.showPauseForPrompt(context); + List values = await NZBGetDialogs.pauseFor(context); if(values[0]) { if(values[1] == -1) { - List values = await LSDialogNZBGet.showCustomPauseForPrompt(context); + List values = await NZBGetDialogs.customPauseFor(context); if(values[0]) await NZBGetAPI.from(Database.currentProfileObject).pauseQueueFor(values[1]) .then((_) => LSSnackBar( context: context, diff --git a/lib/modules/nzbget/widgets/queue_tile.dart b/lib/modules/nzbget/widgets/queue_tile.dart index e52ccf87..8d6db082 100644 --- a/lib/modules/nzbget/widgets/queue_tile.dart +++ b/lib/modules/nzbget/widgets/queue_tile.dart @@ -58,7 +58,7 @@ class _State extends State { Future _handlePopup() async { _Helper _helper = _Helper(widget.queueContext, widget.data, widget.snackbar, widget.refresh); - List values = await LSDialogNZBGet.showQueueSettingsPrompt(widget.queueContext, widget.data.name, widget.data.paused); + List values = await NZBGetDialogs.queueSettings(widget.queueContext, widget.data.name, widget.data.paused); if(values[0]) switch(values[1]) { case 'status': widget.data.paused ? _helper._resumeJob() @@ -123,7 +123,7 @@ class _Helper { Future _category() async { List categories = await NZBGetAPI.from(Database.currentProfileObject).getCategories(); - List values = await LSDialogNZBGet.showCategoryPrompt(context, categories); + List values = await NZBGetDialogs.changeCategory(context, categories); if(values[0]) await NZBGetAPI.from(Database.currentProfileObject).setJobCategory(data.id, values[1]) .then((_) { snackbar( @@ -141,7 +141,7 @@ class _Helper { } Future _priority() async { - List values = await LSDialogNZBGet.showChangePriorityPrompt(context); + List values = await NZBGetDialogs.changePriority(context); if(values[0]) await NZBGetAPI.from(Database.currentProfileObject).setJobPriority(data.id, values[1]) .then((_) { snackbar( @@ -159,7 +159,7 @@ class _Helper { } Future _rename() async { - List values = await LSDialogNZBGet.showRenameJobPrompt(context, data.name); + List values = await NZBGetDialogs.renameJob(context, data.name); if(values[0]) NZBGetAPI.from(Database.currentProfileObject).renameJob(data.id, values[1]) .then((_) { snackbar( @@ -177,7 +177,7 @@ class _Helper { } Future _delete() async { - List values = await LSDialogNZBGet.showDeleteJobPrompt(context); + List values = await NZBGetDialogs.deleteJob(context); if(values[0]) await NZBGetAPI.from(Database.currentProfileObject).deleteJob(data.id) .then((_) { snackbar( @@ -195,7 +195,7 @@ class _Helper { } Future _password() async { - List values = await LSDialogNZBGet.showSetPasswordPrompt(context); + List values = await NZBGetDialogs.setPassword(context); if(values[0]) await NZBGetAPI.from(Database.currentProfileObject).setJobPassword(data.id, values[1]) .then((_) { snackbar( diff --git a/lib/modules/radarr/core.dart b/lib/modules/radarr/core.dart index 49296795..1a5be69e 100644 --- a/lib/modules/radarr/core.dart +++ b/lib/modules/radarr/core.dart @@ -1,4 +1,5 @@ export './core/api.dart'; export './core/constants.dart'; export './core/database.dart'; +export './core/dialogs.dart'; export './core/state.dart'; diff --git a/lib/modules/radarr/core/api/api.dart b/lib/modules/radarr/core/api/api.dart index d49c5489..71083373 100644 --- a/lib/modules/radarr/core/api/api.dart +++ b/lib/modules/radarr/core/api/api.dart @@ -11,12 +11,14 @@ class RadarrAPI extends API { RadarrAPI._internal(this._values, this._dio); factory RadarrAPI.from(ProfileHiveObject profile) { + Map _headers = Map.from(profile.getRadarr()['headers']); Dio _client = Dio( BaseOptions( baseUrl: '${profile.getRadarr()['host']}/api/', queryParameters: { - 'apikey': profile.getRadarr()['key'], + if(profile.getRadarr()['key'] != '') 'apikey': profile.getRadarr()['key'], }, + headers: _headers, followRedirects: true, maxRedirects: 5, ), diff --git a/lib/modules/radarr/core/constants.dart b/lib/modules/radarr/core/constants.dart index 11cdd7dd..1b652d71 100644 --- a/lib/modules/radarr/core/constants.dart +++ b/lib/modules/radarr/core/constants.dart @@ -4,7 +4,9 @@ import 'package:lunasea/modules/radarr.dart'; class RadarrConstants { RadarrConstants._(); - static const Map SERVICE_MAP = { + static const String MODULE_KEY = 'radarr'; + + static const Map MODULE_MAP = { 'name': 'Radarr', 'desc': 'Manage Movies', 'icon': CustomIcons.movies, diff --git a/lib/modules/radarr/core/dialogs.dart b/lib/modules/radarr/core/dialogs.dart new file mode 100644 index 00000000..edd21212 --- /dev/null +++ b/lib/modules/radarr/core/dialogs.dart @@ -0,0 +1,269 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/radarr.dart'; + +class RadarrDialogs { + RadarrDialogs._(); + + static Future> deleteMovieFile(BuildContext context) async { + bool _flag = false; + + void _setValues(bool flag) { + _flag = flag; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Delete Movie File', + buttons: [ + LSDialog.button( + text: 'Delete', + textColor: LSColors.red, + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.textContent(text: 'Are you sure you want to delete this movie file?'), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag]; + } + + static Future> deleteMovie(BuildContext context) async { + bool _flag = false; + bool _files = false; + + void _setValues(bool flag, bool files) { + _flag = flag; + _files = files; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Remove Movie', + buttons: [ + LSDialog.button( + text: 'Remove + Files', + textColor: LSColors.red, + onPressed: () => _setValues(true, true), + ), + LSDialog.button( + text: 'Remove', + textColor: LSColors.red, + onPressed: () => _setValues(true, false), + ), + ], + content: [ + LSDialog.textContent(text: 'Are you sure you want to remove the movie from Radarr?'), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag, _files]; + } + + static Future> searchAllMissing(BuildContext context) async { + bool _flag = false; + + void _setValues(bool flag) { + _flag = flag; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Search All Missing', + buttons: [ + LSDialog.button( + text: 'Search', + textColor: LSColors.accent, + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.textContent(text: 'Are you sure you want to search for all missing movies?'), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag]; + } + + static Future> editMovie(BuildContext context, RadarrCatalogueData entry) async { + List> _options = [ + ['Edit Movie', Icons.edit, 'edit_movie'], + ['Refresh Movie', Icons.refresh, 'refresh_movie'], + ['Remove Movie', Icons.delete, 'remove_movie'], + ]; + bool _flag = false; + String _value = ''; + + void _setValues(bool flag, String value) { + _flag = flag; + _value = value; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: entry.title, + content: List.generate( + _options.length, + (index) => LSDialog.tile( + text: _options[index][0], + icon: _options[index][1], + iconColor: LSColors.list(index), + onTap: () => _setValues(true, _options[index][2]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _value]; + } + + static Future> editMinimumAvailability(BuildContext context, List availability) async { + bool _flag = false; + RadarrAvailability _entry; + + void _setValues(bool flag, RadarrAvailability entry) { + _flag = flag; + _entry = entry; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Minimum Availability', + content: List.generate( + availability.length, + (index) => LSDialog.tile( + text: availability[index].name, + icon: Icons.folder, + iconColor: LSColors.list(index), + onTap: () => _setValues(true, availability[index]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _entry]; + } + + static Future> editRootFolder(BuildContext context, List folders) async { + bool _flag = false; + RadarrRootFolder _folder; + + void _setValues(bool flag, RadarrRootFolder folder) { + _flag = flag; + _folder = folder; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Root Folder', + content: List.generate( + folders.length, + (index) => LSDialog.tile( + text: folders[index].path, + subtitle: LSDialog.richText( + children: [ + LSDialog.bolded(text: folders[index].freeSpace.lsBytes_BytesToString()), + ], + ), + icon: Icons.folder, + iconColor: LSColors.list(index), + onTap: () => _setValues(true, folders[index]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _folder]; + } + + static Future> editQualityProfile(BuildContext context, List qualities) async { + bool _flag = false; + RadarrQualityProfile _quality; + + void _setValues(bool flag, RadarrQualityProfile quality) { + _flag = flag; + _quality = quality; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Quality Profile', + content: List.generate( + qualities.length, + (index) => LSDialog.tile( + text: qualities[index].name, + icon: Icons.portrait, + iconColor: LSColors.list(index), + onTap: () => _setValues(true, qualities[index]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _quality]; + } + + static Future> downloadWarning(BuildContext context) async { + bool _flag = false; + + void _setValues(bool flag) { + _flag = flag; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Download Release', + buttons: [ + LSDialog.button( + text: 'Download', + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.textContent(text: 'Are you sure you want to download this release? It has been marked as a rejected release by Radarr.') + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag]; + } + + static Future> globalSettings(BuildContext context) async { + List> _options = [ + ['View Web GUI', Icons.language, 'web_gui'], + ['Update Library', Icons.autorenew, 'update_library'], + ['Run RSS Sync', Icons.rss_feed, 'rss_sync'], + ['Search All Missing', Icons.search, 'missing_search'], + ['Backup Database', Icons.save, 'backup'], + ]; + bool _flag = false; + String _value = ''; + + void _setValues(bool flag, String value) { + _flag = flag; + _value = value; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Radarr Settings', + content: List.generate( + _options.length, + (index) => LSDialog.tile( + text: _options[index][0], + icon: _options[index][1], + iconColor: LSColors.list(index), + onTap: () => _setValues(true, _options[index][2]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _value]; + } +} diff --git a/lib/modules/radarr/core/state.dart b/lib/modules/radarr/core/state.dart index 7f5ecd99..66deae96 100644 --- a/lib/modules/radarr/core/state.dart +++ b/lib/modules/radarr/core/state.dart @@ -48,4 +48,12 @@ class RadarrModel extends ChangeNotifier { _navigationIndex = navigationIndex; notifyListeners(); } + + int _movieNavigationIndex = 0; + int get movieNavigationIndex => _movieNavigationIndex; + set movieNavigationIndex(int movieNavigationIndex) { + assert(movieNavigationIndex != null); + _movieNavigationIndex = movieNavigationIndex; + notifyListeners(); + } } diff --git a/lib/modules/radarr/routes/add_details.dart b/lib/modules/radarr/routes/add_details.dart index 3b82c9d0..b111c693 100644 --- a/lib/modules/radarr/routes/add_details.dart +++ b/lib/modules/radarr/routes/add_details.dart @@ -138,6 +138,7 @@ class _State extends State { : _arguments.data.overview, uri: _arguments.data.posterURI ?? '', fallbackImage: 'assets/images/radarr/nomovieposter.png', + headers: Database.currentProfileObject.getRadarr()['headers'], ), LSDivider(), ValueListenableBuilder( @@ -162,7 +163,7 @@ class _State extends State { subtitle: LSSubtitle(text: _rootfolder?.path ?? 'Unknown Root Folder'), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async { - List _values = await LSDialogRadarr.showEditRootFolderPrompt(context, _rootFolders); + List _values = await RadarrDialogs.editRootFolder(context, _rootFolders); if(_values[0]) box.put(RadarrDatabaseValue.ADD_ROOT_FOLDER.key, _values[1]); }, ); @@ -177,7 +178,7 @@ class _State extends State { subtitle: LSSubtitle(text: _profile?.name ?? 'Unknown Profile'), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async { - List _values = await LSDialogRadarr.showEditQualityProfilePrompt(context, _qualityProfiles); + List _values = await RadarrDialogs.editQualityProfile(context, _qualityProfiles); if(_values[0]) box.put(RadarrDatabaseValue.ADD_QUALITY_PROFILE.key, _values[1]); }, ); @@ -192,7 +193,7 @@ class _State extends State { subtitle: LSSubtitle(text: _availability?.name ?? 'Unknown Availability'), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async { - List _values = await LSDialogRadarr.showMinimumAvailabilityPrompt(context, RadarrConstants.MINIMUM_AVAILBILITIES); + List _values = await RadarrDialogs.editMinimumAvailability(context, RadarrConstants.MINIMUM_AVAILBILITIES); if(_values[0]) box.put(RadarrDatabaseValue.ADD_AVAILABILITY.key, _values[1]); }, ); diff --git a/lib/modules/radarr/routes/details_movie.dart b/lib/modules/radarr/routes/details_movie.dart index aa5855c1..9730c196 100644 --- a/lib/modules/radarr/routes/details_movie.dart +++ b/lib/modules/radarr/routes/details_movie.dart @@ -22,20 +22,16 @@ class RadarrDetailsMovie extends StatefulWidget { class _State extends State { final GlobalKey _scaffoldKey = GlobalKey(); + final _pageController = PageController(initialPage: 1); RadarrDetailsMovieArguments _arguments; bool _error = false; - final List _tabTitles = [ - 'Overview', - 'Search', - 'Files', - ]; - @override void initState() { super.initState(); SchedulerBinding.instance.addPostFrameCallback((_) { _arguments = ModalRoute.of(context).settings.arguments; + Provider.of(context, listen: false).movieNavigationIndex = 1; _fetch(); }); } @@ -53,7 +49,10 @@ class _State extends State { @override Widget build(BuildContext context) => Scaffold( key: _scaffoldKey, - appBar: _arguments == null || _arguments.data == null ? _appBar : null, + appBar: _appBar, + bottomNavigationBar: _arguments != null && _arguments.data != null + ? _bottomNavigationBar + : null, body: _arguments != null ? _arguments.data != null ? _body @@ -63,36 +62,35 @@ class _State extends State { : null, ); - Widget get _appBar => LSAppBar(title: 'Movie Details'); - - Widget get _body => DefaultTabController( - length: _tabTitles.length, - initialIndex: 1, - child: LSSliverAppBarTabs( - title: _arguments.data.title, - backgroundURI: _arguments.data.fanartURI(highRes: true), - body: TabBarView( - children: [ - RadarrDetailsOverview(data: _arguments.data), - RadarrDetailsSearch(data: _arguments.data), - RadarrDetailsFiles(data: _arguments.data), - ], - ), - bottom: TabBar( - tabs: [ - for(int i =0; i<_tabTitles.length; i++) Tab( - child: Text(_tabTitles[i]), - ), - ], - ), - actions: [ + Widget get _appBar => LSAppBar( + title: _arguments == null || _arguments.data == null + ? 'Movie Details' + : _arguments.data.title, + actions: _arguments == null || _arguments.data == null + ? null + : [ RadarrDetailsEditButton( data: _arguments.data, remove: (bool withData) => _removeCallback(withData), ), ], - ), ); + Widget get _bottomNavigationBar => RadarrMovieNavigationBar(pageController: _pageController); + + List get _tabs => [ + RadarrDetailsOverview(data: _arguments.data), + RadarrDetailsSearch(data: _arguments.data), + RadarrDetailsFiles(data: _arguments.data), + ]; + + Widget get _body => PageView( + controller: _pageController, + children: _tabs, + onPageChanged: _onPageChanged, + ); + + void _onPageChanged(int index) => Provider.of(context, listen: false).movieNavigationIndex = index; + Future _removeCallback(bool withData) async => Navigator.of(context).pop(['remove_movie', withData]); } diff --git a/lib/modules/radarr/routes/details_search_details.dart b/lib/modules/radarr/routes/details_search_details.dart index a7c35e41..69224179 100644 --- a/lib/modules/radarr/routes/details_search_details.dart +++ b/lib/modules/radarr/routes/details_search_details.dart @@ -65,7 +65,7 @@ class _State extends State { title: LSTitle(text: 'Release Title'), subtitle: LSSubtitle(text: _arguments.data.title), trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: () async => LSDialogSystem.textPreview(context, 'Release Title', _arguments.data.title), + onTap: () async => GlobalDialogs.textPreview(context, 'Release Title', _arguments.data.title), ), LSContainerRow( children: [ @@ -156,42 +156,22 @@ class _State extends State { ); Future _download() async { - if(_arguments.data.approved) { - await _startDownload() - .then((_) => LSSnackBar( - context: context, - title: 'Downloading...', - message: _arguments.data.title, - type: SNACKBAR_TYPE.success, - showButton: true, - buttonText: 'Back', - buttonOnPressed: () => Navigator.of(context).popUntil(ModalRoute.withName(Radarr.ROUTE_NAME)), - )) - .catchError((_) => LSSnackBar( - context: context, - title: 'Failed to Start Downloading', - message: Constants.CHECK_LOGS_MESSAGE, - type: SNACKBAR_TYPE.failure, - )); - } else { - List values = await LSDialogRadarr.showDownloadWarningPrompt(context); - if(values[0]) await _startDownload() - .then((_) => LSSnackBar( - context: context, - title: 'Downloading...', - message: _arguments.data.title, - type: SNACKBAR_TYPE.success, - showButton: true, - buttonText: 'Back', - buttonOnPressed: () => Navigator.of(context).popUntil(ModalRoute.withName(Radarr.ROUTE_NAME)), - )) - .catchError((_) => LSSnackBar( - context: context, - title: 'Failed to Start Downloading', - message: Constants.CHECK_LOGS_MESSAGE, - type: SNACKBAR_TYPE.failure, - )); - } + await _startDownload() + .then((_) => LSSnackBar( + context: context, + title: 'Downloading...', + message: _arguments.data.title, + type: SNACKBAR_TYPE.success, + showButton: true, + buttonText: 'Back', + buttonOnPressed: () => Navigator.of(context).popUntil(ModalRoute.withName(Radarr.ROUTE_NAME)), + )) + .catchError((_) => LSSnackBar( + context: context, + title: 'Failed to Start Downloading', + message: Constants.CHECK_LOGS_MESSAGE, + type: SNACKBAR_TYPE.failure, + )); } Future _startDownload() async { @@ -204,6 +184,6 @@ class _State extends State { for(var i=0; i<_arguments.data.rejections.length; i++) { reject += '${i+1}. ${_arguments.data.rejections[i]}\n'; } - await LSDialogSystem.textPreview(context, 'Rejection Reasons', reject.substring(0, reject.length-1)); + await GlobalDialogs.textPreview(context, 'Rejection Reasons', reject.substring(0, reject.length-1)); } } diff --git a/lib/modules/radarr/routes/edit_movie.dart b/lib/modules/radarr/routes/edit_movie.dart index 059f97f4..1a59ad7e 100644 --- a/lib/modules/radarr/routes/edit_movie.dart +++ b/lib/modules/radarr/routes/edit_movie.dart @@ -140,17 +140,17 @@ class _State extends State { ); Future _changePath() async { - List _values = await LSDialogSystem.editText(context, 'Movie Path', prefill: _path); + List _values = await GlobalDialogs.editText(context, 'Movie Path', prefill: _path); if(_values[0] && mounted) setState(() => _path = _values[1]); } Future _changeProfile() async { - List _values = await LSDialogRadarr.showEditQualityProfilePrompt(context, _qualityProfiles); + List _values = await RadarrDialogs.editQualityProfile(context, _qualityProfiles); if(_values[0] && mounted) setState(() => _qualityProfile = _values[1]); } Future _changeMinimumAvailability() async { - List _values = await LSDialogRadarr.showMinimumAvailabilityPrompt(context, RadarrConstants.MINIMUM_AVAILBILITIES); + List _values = await RadarrDialogs.editMinimumAvailability(context, RadarrConstants.MINIMUM_AVAILBILITIES); if(_values[0] && mounted) setState(() => _minimumAvailability = _values[1]); } diff --git a/lib/modules/radarr/routes/radarr.dart b/lib/modules/radarr/routes/radarr.dart index fcf0ef0b..3d10e6b1 100644 --- a/lib/modules/radarr/routes/radarr.dart +++ b/lib/modules/radarr/routes/radarr.dart @@ -29,18 +29,30 @@ class _State extends State { } @override - Widget build(BuildContext context) => ValueListenableBuilder( - valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]), - builder: (context, box, widget) { - if(_profileState != Database.currentProfileObject.toString()) _refreshProfile(); - return Scaffold( - key: _scaffoldKey, - body: _body, - drawer: _drawer, - appBar: _appBar, - bottomNavigationBar: _bottomNavigationBar, - ); + Widget build(BuildContext context) => WillPopScope( + onWillPop: () async { + if(_scaffoldKey.currentState.isDrawerOpen) { + //If the drawer is open, return true to close it + return true; + } else { + //If the drawer isn't open, open the drawer + _scaffoldKey.currentState.openDrawer(); + return false; + } }, + child: ValueListenableBuilder( + valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]), + builder: (context, box, widget) { + if(_profileState != Database.currentProfileObject.toString()) _refreshProfile(); + return Scaffold( + key: _scaffoldKey, + body: _body, + drawer: _drawer, + appBar: _appBar, + bottomNavigationBar: _bottomNavigationBar, + ); + }, + ), ); Widget get _drawer => LSDrawer(page: 'radarr'); @@ -108,7 +120,7 @@ class _State extends State { } Future _handlePopup() async { - List values = await LSDialogRadarr.showSettingsPrompt(context); + List values = await RadarrDialogs.globalSettings(context); if(values[0]) switch(values[1]) { case 'web_gui': await _api.host?.toString()?.lsLinks_OpenLink(); break; case 'update_library': await _api.updateLibrary() @@ -124,7 +136,7 @@ class _State extends State { .catchError((_) => LSSnackBar(context: context, title: 'Failed to Backup Database', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure)); break; case 'missing_search': { - List values = await LSDialogRadarr.showSearchMissingPrompt(context); + List values = await RadarrDialogs.searchAllMissing(context); if(values[0]) await _api.searchAllMissing() .then((_) => LSSnackBar(context: context, title: 'Searching...', message: 'Search for all missing movies')) .catchError((_) => LSSnackBar(context: context, title: 'Failed to Search', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure)); diff --git a/lib/modules/radarr/widgets.dart b/lib/modules/radarr/widgets.dart index 1bdadec0..d4f12eb2 100644 --- a/lib/modules/radarr/widgets.dart +++ b/lib/modules/radarr/widgets.dart @@ -14,3 +14,4 @@ export './widgets/details_overview.dart'; export './widgets/add_search_bar.dart'; export './widgets/add_search_result_tile.dart'; export './widgets/navigation_bar.dart'; +export './widgets/movie_navigation_bar.dart'; diff --git a/lib/modules/radarr/widgets/catalogue_tile.dart b/lib/modules/radarr/widgets/catalogue_tile.dart index 450cd422..17868bff 100644 --- a/lib/modules/radarr/widgets/catalogue_tile.dart +++ b/lib/modules/radarr/widgets/catalogue_tile.dart @@ -87,6 +87,7 @@ class _State extends State { decoration: LSCardBackground( uri: widget.data.posterURI(), darken: !widget.data.monitored, + headers: Database.currentProfileObject.getRadarr()['headers'], ), ); @@ -137,7 +138,7 @@ class _State extends State { } Future _handlePopup() async { - List values = await LSDialogRadarr.showEditMoviePrompt(context, widget.data); + List values = await RadarrDialogs.editMovie(context, widget.data); if(values[0]) switch(values[1]) { case 'refresh_movie': _refreshMovie(); break; case 'edit_movie': _editMovie(); break; @@ -170,10 +171,10 @@ class _State extends State { Future _removeMovie() async { final _api = RadarrAPI.from(Database.currentProfileObject); - List values = await LSDialogRadarr.showDeleteMoviePrompt(context); + List values = await RadarrDialogs.deleteMovie(context); if(values[0]) { if(values[1]) { - values = await LSDialogSystem.deleteCatalogueWithFiles(context, widget.data.title); + values = await GlobalDialogs.deleteCatalogueWithFiles(context, widget.data.title); if(values[0]) { await _api.removeMovie(widget.data.movieID, deleteFiles: true) .then((_) { diff --git a/lib/modules/radarr/widgets/details_edit_button.dart b/lib/modules/radarr/widgets/details_edit_button.dart index 380562c9..0ecec9fc 100644 --- a/lib/modules/radarr/widgets/details_edit_button.dart +++ b/lib/modules/radarr/widgets/details_edit_button.dart @@ -25,7 +25,7 @@ class _State extends State { ); Future _handlePopup(BuildContext context) async { - List values = await LSDialogRadarr.showEditMoviePrompt(context, widget.data); + List values = await RadarrDialogs.editMovie(context, widget.data); if(values[0]) switch(values[1]) { case 'refresh_movie': _refreshMovie(context); break; case 'edit_movie': _editMovie(context); break; @@ -56,10 +56,10 @@ class _State extends State { Future _removeMovie(BuildContext context) async { final _api = RadarrAPI.from(Database.currentProfileObject); - List values = await LSDialogRadarr.showDeleteMoviePrompt(context); + List values = await RadarrDialogs.deleteMovie(context); if(values[0]) { if(values[1]) { - values = await LSDialogSystem.deleteCatalogueWithFiles(context, widget.data.title); + values = await GlobalDialogs.deleteCatalogueWithFiles(context, widget.data.title); if(values[0]) { await _api.removeMovie(widget.data.movieID, deleteFiles: true) .then((_) => widget.remove(true)) diff --git a/lib/modules/radarr/widgets/details_files.dart b/lib/modules/radarr/widgets/details_files.dart index d52ea1b3..2207fa42 100644 --- a/lib/modules/radarr/widgets/details_files.dart +++ b/lib/modules/radarr/widgets/details_files.dart @@ -46,6 +46,9 @@ class _State extends State with AutomaticKeepAliveClientMixi TextSpan(text: '${widget.data.movieFile['quality']['quality']['name'] ?? 'Unknown'}'), ], ), + overflow: TextOverflow.fade, + maxLines: 1, + softWrap: false, ), trailing: LSIconButton( icon: Icons.delete, @@ -57,7 +60,7 @@ class _State extends State with AutomaticKeepAliveClientMixi Widget get _empty => LSGenericMessage(text: 'No Files Found'); Future _delete() async { - List values = await LSDialogRadarr.showDeleteMovieFilePrompt(context); + List values = await RadarrDialogs.deleteMovieFile(context); RadarrAPI _api = RadarrAPI.from(Database.currentProfileObject); if(values[0]) _api.removeMovieFile(widget.data.movieFile['id']) .then((_) { diff --git a/lib/modules/radarr/widgets/details_overview.dart b/lib/modules/radarr/widgets/details_overview.dart index 5c284367..ead9b44c 100644 --- a/lib/modules/radarr/widgets/details_overview.dart +++ b/lib/modules/radarr/widgets/details_overview.dart @@ -30,11 +30,12 @@ class _State extends State with AutomaticKeepAliveClientM : widget?.data?.overview, uri: widget?.data?.posterURI() ?? '', fallbackImage: 'assets/images/radarr/nomovieposter.png', + headers: Database.currentProfileObject.getRadarr()['headers'], ), LSCardTile( title: LSTitle(text: 'Movie Path', centerText: true), subtitle: LSSubtitle(text: widget?.data?.path ?? 'Unknown', centerText: true), - onTap: () => LSDialogSystem.textPreview(context, widget?.data?.title, widget?.data?.path ?? 'Unknown'), + onTap: () => GlobalDialogs.textPreview(context, widget?.data?.title, widget?.data?.path ?? 'Unknown'), ), LSContainerRow( children: [ diff --git a/lib/modules/radarr/widgets/details_search_tile.dart b/lib/modules/radarr/widgets/details_search_tile.dart index 7e71af98..22ba0a86 100644 --- a/lib/modules/radarr/widgets/details_search_tile.dart +++ b/lib/modules/radarr/widgets/details_search_tile.dart @@ -84,7 +84,7 @@ class RadarrDetailsSearchTile extends StatelessWidget { Future _trailingLongPressed(BuildContext context) async { if(!data.approved) { - List values = await LSDialogRadarr.showDownloadWarningPrompt(context); + List values = await RadarrDialogs.downloadWarning(context); if(values[0]) await _startDownload() .then((_) => LSSnackBar( context: context, @@ -114,6 +114,6 @@ class RadarrDetailsSearchTile extends StatelessWidget { for(var i=0; i { onPressed: () async => _search(), ), onTap: () async => _enterMovie(), - decoration: LSCardBackground(uri: widget.data.posterURI()), + decoration: LSCardBackground( + uri: widget.data.posterURI(), + headers: Database.currentProfileObject.getRadarr()['headers'], + ), padContent: true, ); diff --git a/lib/modules/radarr/widgets/movie_navigation_bar.dart b/lib/modules/radarr/widgets/movie_navigation_bar.dart new file mode 100644 index 00000000..8728919c --- /dev/null +++ b/lib/modules/radarr/widgets/movie_navigation_bar.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import '../../radarr.dart'; + +class RadarrMovieNavigationBar extends StatefulWidget { + final PageController pageController; + + RadarrMovieNavigationBar({ + Key key, + @required this.pageController, + }): super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State { + static const List _navbarTitles = [ + 'Overview', + 'Search', + 'Files', + ]; + + static const List _navbarIcons = [ + Icons.subject, + CustomIcons.movies, + Icons.insert_drive_file, + ]; + + @override + Widget build(BuildContext context) => Selector( + selector: (_, model) => model.movieNavigationIndex, + builder: (context, index, _) => LSBottomNavigationBar( + index: index, + icons: _navbarIcons, + titles: _navbarTitles, + onTap: (index) async => await _navOnTap(index), + ), + ); + + Future _navOnTap(int index) async { + await widget.pageController.animateToPage( + index, + duration: Duration(milliseconds: Constants.UI_NAVIGATION_SPEED), + curve: Curves.easeOutSine, + ).then((_) => Provider.of(context, listen: false).movieNavigationIndex = index); + } +} diff --git a/lib/modules/radarr/widgets/upcoming_tile.dart b/lib/modules/radarr/widgets/upcoming_tile.dart index f488d84b..d7ad6791 100644 --- a/lib/modules/radarr/widgets/upcoming_tile.dart +++ b/lib/modules/radarr/widgets/upcoming_tile.dart @@ -35,7 +35,10 @@ class _State extends State { ), onTap: () async => _enterMovie(), padContent: true, - decoration: LSCardBackground(uri: widget.data.posterURI()), + decoration: LSCardBackground( + uri: widget.data.posterURI(), + headers: Database.currentProfileObject.getRadarr()['headers'], + ), ); Future _search() async { diff --git a/lib/modules/sabnzbd.dart b/lib/modules/sabnzbd.dart index 5b25b5c4..cc67eead 100644 --- a/lib/modules/sabnzbd.dart +++ b/lib/modules/sabnzbd.dart @@ -1,4 +1,3 @@ -export './sabnzbd/api.dart'; +export './sabnzbd/core.dart'; export './sabnzbd/routes.dart'; -export './sabnzbd/state.dart'; export './sabnzbd/widgets.dart'; diff --git a/lib/modules/sabnzbd/core.dart b/lib/modules/sabnzbd/core.dart new file mode 100644 index 00000000..1a5be69e --- /dev/null +++ b/lib/modules/sabnzbd/core.dart @@ -0,0 +1,5 @@ +export './core/api.dart'; +export './core/constants.dart'; +export './core/database.dart'; +export './core/dialogs.dart'; +export './core/state.dart'; diff --git a/lib/modules/sabnzbd/api.dart b/lib/modules/sabnzbd/core/api.dart similarity index 100% rename from lib/modules/sabnzbd/api.dart rename to lib/modules/sabnzbd/core/api.dart diff --git a/lib/modules/sabnzbd/api/api.dart b/lib/modules/sabnzbd/core/api/api.dart similarity index 98% rename from lib/modules/sabnzbd/api/api.dart rename to lib/modules/sabnzbd/core/api/api.dart index 06513e3e..800f8555 100644 --- a/lib/modules/sabnzbd/api/api.dart +++ b/lib/modules/sabnzbd/core/api/api.dart @@ -2,7 +2,7 @@ import 'dart:io'; import 'package:dio/adapter.dart'; import 'package:dio/dio.dart'; import 'package:lunasea/core.dart'; -import '../../sabnzbd.dart'; +import 'package:lunasea/modules/sabnzbd.dart'; class SABnzbdAPI extends API { final Map _values; @@ -10,13 +10,15 @@ class SABnzbdAPI extends API { SABnzbdAPI._internal(this._values, this._dio); factory SABnzbdAPI.from(ProfileHiveObject profile) { + Map _headers = Map.from(profile.getSABnzbd()['headers']); Dio _client = Dio( BaseOptions( baseUrl: '${profile.getSABnzbd()['host']}/api', queryParameters: { - 'apikey': profile.getSABnzbd()['key'], + if(profile.getSABnzbd()['key'] != '') 'apikey': profile.getSABnzbd()['key'], 'output': 'json', }, + headers: _headers, followRedirects: true, maxRedirects: 5, ), @@ -253,7 +255,7 @@ class SABnzbdAPI extends API { Future setJobPriority(String nzoId, int priority) async { try { - Response response = await _dio.get( + await _dio.get( '', queryParameters: { 'mode': 'queue', @@ -262,9 +264,7 @@ class SABnzbdAPI extends API { 'value2': priority, } ); - return response.data['position'] != null - ? true - : Future.error(null); + return true; } catch (error) { logError('setJobPriority', 'Failed to set job priority ($nzoId, $priority)', error); return Future.error(error); diff --git a/lib/modules/sabnzbd/api/data.dart b/lib/modules/sabnzbd/core/api/data.dart similarity index 100% rename from lib/modules/sabnzbd/api/data.dart rename to lib/modules/sabnzbd/core/api/data.dart diff --git a/lib/modules/sabnzbd/api/data/category.dart b/lib/modules/sabnzbd/core/api/data/category.dart similarity index 100% rename from lib/modules/sabnzbd/api/data/category.dart rename to lib/modules/sabnzbd/core/api/data/category.dart diff --git a/lib/modules/sabnzbd/api/data/history.dart b/lib/modules/sabnzbd/core/api/data/history.dart similarity index 100% rename from lib/modules/sabnzbd/api/data/history.dart rename to lib/modules/sabnzbd/core/api/data/history.dart diff --git a/lib/modules/sabnzbd/api/data/queue.dart b/lib/modules/sabnzbd/core/api/data/queue.dart similarity index 100% rename from lib/modules/sabnzbd/api/data/queue.dart rename to lib/modules/sabnzbd/core/api/data/queue.dart diff --git a/lib/modules/sabnzbd/api/data/statistics.dart b/lib/modules/sabnzbd/core/api/data/statistics.dart similarity index 100% rename from lib/modules/sabnzbd/api/data/statistics.dart rename to lib/modules/sabnzbd/core/api/data/statistics.dart diff --git a/lib/modules/sabnzbd/api/data/statistics_server.dart b/lib/modules/sabnzbd/core/api/data/statistics_server.dart similarity index 100% rename from lib/modules/sabnzbd/api/data/statistics_server.dart rename to lib/modules/sabnzbd/core/api/data/statistics_server.dart diff --git a/lib/modules/sabnzbd/api/data/status.dart b/lib/modules/sabnzbd/core/api/data/status.dart similarity index 100% rename from lib/modules/sabnzbd/api/data/status.dart rename to lib/modules/sabnzbd/core/api/data/status.dart diff --git a/lib/modules/sabnzbd/core/constants.dart b/lib/modules/sabnzbd/core/constants.dart new file mode 100644 index 00000000..10f4f328 --- /dev/null +++ b/lib/modules/sabnzbd/core/constants.dart @@ -0,0 +1,14 @@ +import 'package:lunasea/core.dart'; + +class SABnzbdConstants { + SABnzbdConstants._(); + + static const String MODULE_KEY = 'sabnzbd'; + + static const Map MODULE_MAP = { + 'name': 'SABnzbd', + 'desc': 'Manage Usenet Downloads', + 'icon': CustomIcons.sabnzbd, + 'route': '/sabnzbd', + }; +} diff --git a/lib/modules/sabnzbd/core/database.dart b/lib/modules/sabnzbd/core/database.dart new file mode 100644 index 00000000..1a8169c1 --- /dev/null +++ b/lib/modules/sabnzbd/core/database.dart @@ -0,0 +1,3 @@ +class SABnzbdDatabase { + SABnzbdDatabase._(); +} diff --git a/lib/modules/sabnzbd/core/dialogs.dart b/lib/modules/sabnzbd/core/dialogs.dart new file mode 100644 index 00000000..aef26d94 --- /dev/null +++ b/lib/modules/sabnzbd/core/dialogs.dart @@ -0,0 +1,656 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sabnzbd.dart'; + +class SABnzbdDialogs { + SABnzbdDialogs._(); + + static Future> globalSettings(BuildContext context) async { + List> _options = [ + ['View Web GUI', Icons.language, 'web_gui'], + ['Add NZB', Icons.add, 'add_nzb'], + ['Sort Queue', Icons.sort, 'sort'], + ['Clear History', Icons.clear_all, 'clear_history'], + ['On Complete Action', Icons.settings_power, 'complete_action'], + ['Status & Statistics', Icons.info_outline, 'server_details'], + ]; + bool _flag = false; + String _value = ''; + + void _setValues(bool flag, String value) { + _flag = flag; + _value = value; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'SABnzbd Settings', + content: List.generate( + _options.length, + (index) => LSDialog.tile( + text: _options[index][0], + icon: _options[index][1], + iconColor: LSColors.list(index), + onTap: () => _setValues(true, _options[index][2]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _value]; + } + + static Future> queueSettings(BuildContext context, String title, bool isPaused) async { + List> _options = [ + isPaused + ? ['Resume Job', Icons.play_arrow, 'status'] + : ['Pause Job', Icons.pause, 'status'], + ['Change Category', Icons.category, 'category'], + ['Change Priority', Icons.low_priority, 'priority'], + ['Set Password', Icons.vpn_key, 'password'], + ['Rename Job', Icons.text_format, 'rename'], + ['Delete Job', Icons.delete, 'delete'], + ]; + bool _flag = false; + String _value = ''; + + void _setValues(bool flag, String value) { + _flag = flag; + _value = value; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: title, + content: List.generate( + _options.length, + (index) => LSDialog.tile( + text: _options[index][0], + icon: _options[index][1], + iconColor: LSColors.list(index), + onTap: () => _setValues(true, _options[index][2]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _value]; + } + + static Future> historySettings(BuildContext context, String title, bool failed) async { + List> _options = [ + if(failed) ['Retry Job', Icons.autorenew, 'retry'], + if(failed) ['Set Password', Icons.vpn_key, 'password'], + ['Delete History', Icons.delete, 'delete'], + ]; + bool _flag = false; + String _value = ''; + + void _setValues(bool flag, String value) { + _flag = flag; + _value = value; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: title, + content: List.generate( + _options.length, + (index) => LSDialog.tile( + text: _options[index][0], + icon: _options[index][1], + iconColor: _options.length == 1 ? LSColors.red : LSColors.list(index), + onTap: () => _setValues(true, _options[index][2]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _value]; + } + + static Future> changeCategory(BuildContext context, List categories) async { + bool _flag = false; + String _value = ''; + + void _setValues(bool flag, String value) { + _flag = flag; + _value = value; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Change Category', + content: List.generate( + categories.length, + (index) => LSDialog.tile( + text: categories[index].category, + icon: Icons.category, + iconColor: LSColors.list(index), + onTap: () => _setValues(true, categories[index].category), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _value]; + } + + static Future> sortQueue(BuildContext context) async { + List> _options = [ + ['Age (Ascending)', Icons.access_time, 'avg_age', 'asc'], + ['Age (Descending)', Icons.access_time, 'avg_age', 'desc'], + ['Name (Ascending)', Icons.text_rotate_vertical, 'name', 'asc'], + ['Name (Descending)', Icons.text_rotate_vertical, 'name', 'desc'], + ['Size (Ascending)', Icons.sd_card, 'size', 'asc'], + ['Size (Descending)', Icons.sd_card, 'size', 'desc'], + ]; + bool _flag = false; + String _sort = ''; + String _dir = ''; + String _name = ''; + + void _setValues(bool flag, String sort, String dir, String name) { + _flag = flag; + _sort = sort; + _dir = dir; + _name = name; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Sort Queue', + content: List.generate( + _options.length, + (index) => LSDialog.tile( + text: _options[index][0], + icon: _options[index][1], + iconColor: LSColors.list(index), + onTap: () => _setValues(true, _options[index][2], _options[index][3], _options[index][0]) + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _sort, _dir, _name]; + } + + static Future> addNZB(BuildContext context) async { + List> _options = [ + ['Add by URL', Icons.link, 'link'], + ['Add by File', Icons.sd_card, 'file'], + ]; + bool _flag = false; + String _value = ''; + void _setValues(bool flag, String value) { + _flag = flag; + _value = value; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Add NZB', + content: List.generate( + _options.length, + (index) => LSDialog.tile( + text: _options[index][0], + icon: _options[index][1], + iconColor: LSColors.list(index), + onTap: () => _setValues(true, _options[index][2]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _value]; + } + + static Future> addNZBUrl(BuildContext context) async { + bool _flag = false; + final _formKey = GlobalKey(); + final _textController = TextEditingController(); + + void _setValues(bool flag) { + if(_formKey.currentState.validate()) { + _flag = flag; + Navigator.of(context).pop(); + } + } + + await LSDialog.dialog( + context: context, + title: 'Add NZB by URL', + buttons: [ + LSDialog.button( + text: 'Add', + onPressed: () => _setValues(true), + ), + ], + content: [ + Form( + key: _formKey, + child: LSDialog.textFormInput( + controller: _textController, + title: 'NZB URL', + keyboardType: TextInputType.url, + onSubmitted: (_) => _setValues(true), + validator: (value) => !value.startsWith('http://') && !value.startsWith('https://') + ? 'Please enter a valid URL' + : null, + ), + ), + ], + contentPadding: LSDialog.inputDialogContentPadding(), + ); + return [_flag, _textController.text]; + } + + static Future> renameJob(BuildContext context, String originalName) async { + bool _flag = false; + final _formKey = GlobalKey(); + final _textController = TextEditingController()..text = originalName; + + void _setValues(bool flag) { + if(_formKey.currentState.validate()) { + _flag = flag; + Navigator.of(context).pop(); + } + } + + await LSDialog.dialog( + context: context, + title: 'Rename Job', + buttons: [ + LSDialog.button( + text: 'Rename', + onPressed: () => _setValues(true), + ), + ], + content: [ + Form( + key: _formKey, + child: LSDialog.textFormInput( + controller: _textController, + title: 'Job Name', + onSubmitted: (_) => _setValues(true), + validator: (value) => value.length < 1 + ? 'Please enter a valid name' + : null, + ), + ), + ], + contentPadding: LSDialog.inputDialogContentPadding(), + ); + return [_flag, _textController.text]; + } + + static Future> setPassword(BuildContext context) async { + bool _flag = false; + final _formKey = GlobalKey(); + final _textController = TextEditingController(); + + void _setValues(bool flag) { + if(_formKey.currentState.validate()) { + _flag = flag; + Navigator.of(context).pop(); + } + } + + await LSDialog.dialog( + context: context, + title: 'Set Password', + buttons: [ + LSDialog.button( + text: 'Set', + onPressed: () => _setValues(true), + ), + ], + content: [ + Form( + key: _formKey, + child: LSDialog.textFormInput( + controller: _textController, + title: 'Job Password', + onSubmitted: (_) => _setValues(true), + validator: (value) => value.length < 1 + ? 'Please enter a valid password' + : null, + obscureText: true, + ), + ), + ], + contentPadding: LSDialog.inputDialogContentPadding(), + ); + return [_flag, _textController.text]; + } + + static Future> speedLimit(BuildContext context, int currentSpeed) async { + List> _options = [ + ['20%', Icons.timeline, 20], + ['40%', Icons.timeline, 40], + ['60%', Icons.timeline, 60], + ['80%', Icons.timeline, 80], + ['100%', Icons.timeline, 100], + ['Custom...', Icons.timeline, -1], + ]; + bool _flag = false; + int _limit = 0; + + void _setValues(bool flag, int limit) { + _flag = flag; + _limit = limit; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Speed Limit ($currentSpeed%)', + content: List.generate( + _options.length, + (index) => LSDialog.tile( + text: _options[index][0], + icon: _options[index][1], + iconColor: LSColors.list(index), + onTap: () => _setValues(true, _options[index][2]) + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _limit]; + } + + static Future> pauseFor(BuildContext context) async { + List> _options = [ + ['5 Minutes', Icons.access_time, 5], + ['15 Minutes', Icons.access_time, 15], + ['30 Minutes', Icons.access_time, 30], + ['1 Hour', Icons.access_time, 60], + ['3 Hours', Icons.access_time, 180], + ['6 Hours', Icons.access_time, 360], + ['Custom...', Icons.access_time, -1], + ]; + bool _flag = false; + int _duration = 0; + + void _setValues(bool flag, int duration) { + _flag = flag; + _duration = duration; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Pause Queue For...', + content: List.generate( + _options.length, + (index) => LSDialog.tile( + text: _options[index][0], + icon: _options[index][1], + iconColor: LSColors.list(index), + onTap: () => _setValues(true, _options[index][2]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _duration]; + } + + static Future> customPauseFor(BuildContext context) async { + bool _flag = false; + final _formKey = GlobalKey(); + final _textController = TextEditingController(); + + void _setValues(bool flag) { + if(_formKey.currentState.validate()) { + _flag = flag; + Navigator.of(context).pop(); + } + } + + await LSDialog.dialog( + context: context, + title: 'Custom Pause Duration', + buttons: [ + LSDialog.button( + text: 'Pause', + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.richText( + children: [ + LSDialog.textSpanContent(text: 'Please enter how long in '), + LSDialog.bolded(text: 'minutes'), + LSDialog.textSpanContent(text: ' you want to pause the queue for.'), + ], + alignment: TextAlign.center, + ), + Form( + key: _formKey, + child: LSDialog.textFormInput( + controller: _textController, + title: 'Pause Duration in Minutes', + keyboardType: TextInputType.number, + onSubmitted: (_) => _setValues(true), + validator: (value) { + int _value = int.tryParse(value); + if(_value == null || _value < 1) { + return 'Must be at least 1'; + } + return null; + } + ), + ), + ], + contentPadding: LSDialog.inputTextDialogContentPadding(), + ); + return [_flag, int.tryParse(_textController.text)]; + } + + static Future> changePriority(BuildContext context) async { + List> _options = [ + ['Category Default', Icons.low_priority, -100], + ['Force', Icons.low_priority, 2], + ['High', Icons.low_priority, 1], + ['Normal', Icons.low_priority, 0], + ['Low', Icons.low_priority, -1], + ['Stop', Icons.low_priority, -4], + ]; + bool _flag = false; + int _priority = 0; + String _name = ''; + + void _setValues(bool flag, int priority, String name) { + _flag = flag; + _priority = priority; + _name = name; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Change Priority', + content: List.generate( + _options.length, + (index) => LSDialog.tile( + text: _options[index][0], + icon: _options[index][1], + iconColor: LSColors.list(index), + onTap: () => _setValues(true, _options[index][2], _options[index][0]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _priority, _name]; + } + + static Future> changeOnCompleteAction(BuildContext context) async { + List> _options = [ + ['Shutdown SABnzbd', Icons.settings_power, 'shutdown_program'], + ['Shutdown Machine', Icons.settings_power, 'shutdown_pc'], + ['Hibernate Machine', Icons.settings_power, 'hibernate_pc'], + ['Standby Machine', Icons.settings_power, 'standby_pc'], + ['Nothing', Icons.settings_power, 'none'], + ]; + bool _flag = false; + String _action = ''; + String _name = ''; + + void _setValues(bool flag, String action, String name) { + _flag = flag; + _action = action; + _name = name; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'On Complete Action', + content: List.generate( + _options.length, + (index) => LSDialog.tile( + text: _options[index][0], + icon: _options[index][1], + iconColor: LSColors.list(index), + onTap: () => _setValues(true, _options[index][2], _options[index][0]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _action, _name]; + } + + static Future> clearAllHistory(BuildContext context) async { + List> _options = [ + ['Clear All', Icons.clear_all, 'all', false], + ['Clear Completed', Icons.clear_all, 'completed', false], + ['Clear Failed', Icons.clear_all, 'failed', false], + ['Clear & Delete Failed', Icons.clear_all, 'failed', true], + ]; + bool _flag = false; + bool _delete = false; + String _action = ''; + String _name = ''; + + void _setValues(bool flag, String action, bool delete, String name) { + _flag = flag; + _action = action; + _delete = delete; + _name = name; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Clear History', + content: List.generate( + _options.length, + (index) => LSDialog.tile( + text: _options[index][0], + icon: _options[index][1], + iconColor: LSColors.list(index), + onTap: () => _setValues(true, _options[index][2], _options[index][3], _options[index][0]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _action, _delete, _name]; + } + + static Future> customSpeedLimit(BuildContext context) async { + bool _flag = false; + final _formKey = GlobalKey(); + final _textController = TextEditingController(); + + void _setValues(bool flag) { + if(_formKey.currentState.validate()) { + _flag = flag; + Navigator.of(context).pop(); + } + } + + await LSDialog.dialog( + context: context, + title: 'Custom Speed Limit', + buttons: [ + LSDialog.button( + text: 'Set', + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.textContent(text: 'Please enter a percentage between 1 and 100.'), + Form( + key: _formKey, + child: LSDialog.textFormInput( + controller: _textController, + title: 'Speed Limit', + keyboardType: TextInputType.number, + onSubmitted: (_) => _setValues(true), + validator: (value) { + int _value = int.tryParse(value); + if(_value == null || _value < 1 || _value > 100) { + return 'Must be between 1 and 100'; + } + return null; + }, + ), + ), + ], + contentPadding: LSDialog.inputTextDialogContentPadding(), + ); + return [_flag, int.tryParse(_textController.text)]; + } + + static Future> deleteJob(BuildContext context) async { + bool _flag = false; + + void _setValues(bool flag) { + _flag = flag; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Delete Job', + buttons: [ + LSDialog.button( + text: 'Delete', + textColor: LSColors.red, + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.textContent(text: 'Are you sure you want to delete this job?'), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag]; + } + + static Future> deleteHistory(BuildContext context) async { + bool _flag = false; + + void _setValues(bool flag) { + _flag = flag; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Delete History', + buttons: [ + LSDialog.button( + text: 'Delete', + textColor: LSColors.red, + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.textContent(text: 'Are you sure you want to delete the history for this job?'), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag]; + } +} diff --git a/lib/modules/sabnzbd/state.dart b/lib/modules/sabnzbd/core/state.dart similarity index 100% rename from lib/modules/sabnzbd/state.dart rename to lib/modules/sabnzbd/core/state.dart diff --git a/lib/modules/sabnzbd/routes/history_details.dart b/lib/modules/sabnzbd/routes/history_details.dart index 4624dab4..e031a4e5 100644 --- a/lib/modules/sabnzbd/routes/history_details.dart +++ b/lib/modules/sabnzbd/routes/history_details.dart @@ -94,7 +94,7 @@ class _State extends State { title: LSTitle(text: 'Storage Location'), subtitle: LSSubtitle(text: _arguments.data.storageLocation), trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: () async => LSDialogSystem.textPreview(context, 'Storage Location', _arguments.data.storageLocation), + onTap: () async => GlobalDialogs.textPreview(context, 'Storage Location', _arguments.data.storageLocation), ), ]; @@ -106,7 +106,7 @@ class _State extends State { trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async { String _data = stage['actions'].join(',\n').replaceAll('
', '.\n'); - LSDialogSystem.textPreview(context, stage['name'], _data); + GlobalDialogs.textPreview(context, stage['name'], _data); } ), ]; @@ -121,7 +121,7 @@ class _State extends State { ]; Future _delete() async { - List values = await LSDialogSABnzbd.showDeleteHistoryPrompt(context); + List values = await SABnzbdDialogs.deleteHistory(context); if(values[0]) await SABnzbdAPI.from(Database.currentProfileObject).deleteHistory(_arguments.data.nzoId) .then((_) { Navigator.of(context).pop(['delete']); diff --git a/lib/modules/sabnzbd/routes/sabnzbd.dart b/lib/modules/sabnzbd/routes/sabnzbd.dart index baf5da3a..06fd72f4 100644 --- a/lib/modules/sabnzbd/routes/sabnzbd.dart +++ b/lib/modules/sabnzbd/routes/sabnzbd.dart @@ -29,18 +29,30 @@ class _State extends State { } @override - Widget build(BuildContext context) => ValueListenableBuilder( - valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]), - builder: (context, box, widget) { - if(_profileState != Database.currentProfileObject.toString()) _refreshProfile(); - return Scaffold( - key: _scaffoldKey, - body: _body, - drawer: _drawer, - appBar: _appBar, - bottomNavigationBar: _bottomNavigationBar, - ); + Widget build(BuildContext context) => WillPopScope( + onWillPop: () async { + if(_scaffoldKey.currentState.isDrawerOpen) { + //If the drawer is open, return true to close it + return true; + } else { + //If the drawer isn't open, open the drawer + _scaffoldKey.currentState.openDrawer(); + return false; + } }, + child: ValueListenableBuilder( + valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]), + builder: (context, box, widget) { + if(_profileState != Database.currentProfileObject.toString()) _refreshProfile(); + return Scaffold( + key: _scaffoldKey, + body: _body, + drawer: _drawer, + appBar: _appBar, + bottomNavigationBar: _bottomNavigationBar, + ); + }, + ), ); Widget get _drawer => LSDrawer(page: 'sabnzbd'); @@ -82,7 +94,7 @@ class _State extends State { ); Future _handlePopup() async { - List values = await LSDialogSABnzbd.showSettingsPrompt(context); + List values = await SABnzbdDialogs.globalSettings(context); if(values[0]) switch(values[1]) { case 'web_gui': _api.host.lsLinks_OpenLink(); break; case 'add_nzb': _addNZB(); break; @@ -97,7 +109,7 @@ class _State extends State { Future _serverDetails() async => Navigator.of(context).pushNamed(SABnzbdStatistics.ROUTE_NAME); Future _completeAction() async { - List values = await LSDialogSABnzbd.showOnCompletePrompt(context); + List values = await SABnzbdDialogs.changeOnCompleteAction(context); if(values[0]) SABnzbdAPI.from(Database.currentProfileObject).setOnCompleteAction(values[1]) .then((_) => LSSnackBar( context: context, @@ -114,7 +126,7 @@ class _State extends State { } Future _clearHistory() async { - List values = await LSDialogSABnzbd.showClearHistoryPrompt(context); + List values = await SABnzbdDialogs.clearAllHistory(context); if(values[0]) SABnzbdAPI.from(Database.currentProfileObject).clearHistory(values[1], values[2]) .then((_) { LSSnackBar( @@ -134,7 +146,7 @@ class _State extends State { } Future _sort() async { - List values = await LSDialogSABnzbd.showSortPrompt(context); + List values = await SABnzbdDialogs.sortQueue(context); if(values[0]) await SABnzbdAPI.from(Database.currentProfileObject).sortQueue(values[1], values[2]) .then((_) { LSSnackBar( @@ -154,7 +166,7 @@ class _State extends State { } Future _addNZB() async { - List values = await LSDialogSABnzbd.showAddNZBPrompt(context); + List values = await SABnzbdDialogs.addNZB(context); if(values[0]) switch(values[1]) { case 'link': _addByURL(); break; case 'file': _addByFile(); break; @@ -205,7 +217,7 @@ class _State extends State { } Future _addByURL() async { - List values = await LSDialogSABnzbd.showaddURLPrompt(context); + List values = await SABnzbdDialogs.addNZBUrl(context); if(values[0]) await _api.uploadURL(values[1]) .then((_) => LSSnackBar( context: context, diff --git a/lib/modules/sabnzbd/widgets/app_bar_stats.dart b/lib/modules/sabnzbd/widgets/app_bar_stats.dart index 300b5dc4..7e007c7f 100644 --- a/lib/modules/sabnzbd/widgets/app_bar_stats.dart +++ b/lib/modules/sabnzbd/widgets/app_bar_stats.dart @@ -50,10 +50,10 @@ class SABnzbdAppBarStats extends StatelessWidget { : speed; Future _onTap(BuildContext context, int speed) async { - List values = await LSDialogSABnzbd.showSpeedPrompt(context, speed); + List values = await SABnzbdDialogs.speedLimit(context, speed); if(values[0]) switch(values[1]) { case -1: { - values = await LSDialogSABnzbd.showCustomSpeedPrompt(context); + values = await SABnzbdDialogs.customSpeedLimit(context); if(values[0]) SABnzbdAPI.from(Database.currentProfileObject).setSpeedLimit(values[1]) .then((_) => LSSnackBar( context: context, diff --git a/lib/modules/sabnzbd/widgets/history_tile.dart b/lib/modules/sabnzbd/widgets/history_tile.dart index 8dfbad45..3eb7fffe 100644 --- a/lib/modules/sabnzbd/widgets/history_tile.dart +++ b/lib/modules/sabnzbd/widgets/history_tile.dart @@ -47,7 +47,7 @@ class SABnzbdHistoryTile extends StatelessWidget { } Future _handlePopup(BuildContext context) async { - List values = await LSDialogSABnzbd.showHistorySettingsPrompt(context, data.name, data.failed); + List values = await SABnzbdDialogs.historySettings(context, data.name, data.failed); if(values[0]) switch(values[1]) { case 'retry': _retry(context); break; case 'password': _password(context); break; @@ -68,7 +68,7 @@ class SABnzbdHistoryTile extends StatelessWidget { } Future _password(BuildContext context) async { - List values = await LSDialogSABnzbd.showSetPasswordPrompt(context); + List values = await SABnzbdDialogs.setPassword(context); if(values[0]) SABnzbdAPI.from(Database.currentProfileObject).retryFailedJobPassword(data.nzoId, values[1]) .then((_) => _handleRefresh(context, 'Password Set / Retrying...')) .catchError((_) => LSSnackBar( diff --git a/lib/modules/sabnzbd/widgets/queue_fab.dart b/lib/modules/sabnzbd/widgets/queue_fab.dart index d790c262..027cc80c 100644 --- a/lib/modules/sabnzbd/widgets/queue_fab.dart +++ b/lib/modules/sabnzbd/widgets/queue_fab.dart @@ -27,10 +27,10 @@ class SABnzbdQueueFAB extends StatelessWidget { } Future _toggleFor(BuildContext context) async { - List values = await LSDialogNZBGet.showPauseForPrompt(context); + List values = await SABnzbdDialogs.pauseFor(context); if(values[0]) { if(values[1] == -1) { - List values = await LSDialogNZBGet.showCustomPauseForPrompt(context); + List values = await SABnzbdDialogs.customPauseFor(context); if(values[0]) await SABnzbdAPI.from(Database.currentProfileObject).pauseQueueFor(values[1]) .then((_) => LSSnackBar( context: context, diff --git a/lib/modules/sabnzbd/widgets/queue_tile.dart b/lib/modules/sabnzbd/widgets/queue_tile.dart index bc70f737..b6badd69 100644 --- a/lib/modules/sabnzbd/widgets/queue_tile.dart +++ b/lib/modules/sabnzbd/widgets/queue_tile.dart @@ -58,7 +58,7 @@ class _State extends State { Future _handlePopup() async { _Helper _helper = _Helper(widget.queueContext, widget.data, widget.snackbar, widget.refresh); - List values = await LSDialogSABnzbd.showQueueSettingsPrompt(widget.queueContext, widget.data.name, widget.data.isPaused); + List values = await SABnzbdDialogs.queueSettings(widget.queueContext, widget.data.name, widget.data.isPaused); if(values[0]) switch(values[1]) { case 'status': widget.data.isPaused ? _helper._resumeJob() @@ -123,7 +123,7 @@ class _Helper { Future _category() async { List categories = await SABnzbdAPI.from(Database.currentProfileObject).getCategories(); - List values = await LSDialogSABnzbd.showCategoryPrompt(context, categories); + List values = await SABnzbdDialogs.changeCategory(context, categories); if(values[0]) await SABnzbdAPI.from(Database.currentProfileObject).setCategory(data.nzoId, values[1]) .then((_) { snackbar( @@ -141,7 +141,7 @@ class _Helper { } Future _priority() async { - List values = await LSDialogSABnzbd.showChangePriorityPrompt(context); + List values = await SABnzbdDialogs.changePriority(context); if(values[0]) await SABnzbdAPI.from(Database.currentProfileObject).setJobPriority(data.nzoId, values[1]) .then((_) { snackbar( @@ -159,7 +159,7 @@ class _Helper { } Future _rename() async { - List values = await LSDialogSABnzbd.showRenameJobPrompt(context, data.name); + List values = await SABnzbdDialogs.renameJob(context, data.name); if(values[0]) SABnzbdAPI.from(Database.currentProfileObject).renameJob(data.nzoId, values[1]) .then((_) { snackbar( @@ -177,7 +177,7 @@ class _Helper { } Future _delete() async { - List values = await LSDialogSABnzbd.showDeleteJobPrompt(context); + List values = await SABnzbdDialogs.deleteJob(context); if(values[0]) await SABnzbdAPI.from(Database.currentProfileObject).deleteJob(data.nzoId) .then((_) { snackbar( @@ -195,7 +195,7 @@ class _Helper { } Future _password() async { - List values = await LSDialogSABnzbd.showSetPasswordPrompt(context); + List values = await SABnzbdDialogs.setPassword(context); if(values[0]) await SABnzbdAPI.from(Database.currentProfileObject).setJobPassword(data.nzoId, data.name, values[1]) .then((_) { snackbar( diff --git a/lib/modules/search.dart b/lib/modules/search.dart index 114decd0..a7c2f82e 100644 --- a/lib/modules/search.dart +++ b/lib/modules/search.dart @@ -1,4 +1,3 @@ -export './search/api.dart'; +export './search/core.dart'; export './search/routes.dart'; -export './search/state.dart'; export './search/widgets.dart'; diff --git a/lib/modules/search/core.dart b/lib/modules/search/core.dart new file mode 100644 index 00000000..1a5be69e --- /dev/null +++ b/lib/modules/search/core.dart @@ -0,0 +1,5 @@ +export './core/api.dart'; +export './core/constants.dart'; +export './core/database.dart'; +export './core/dialogs.dart'; +export './core/state.dart'; diff --git a/lib/modules/search/api.dart b/lib/modules/search/core/api.dart similarity index 100% rename from lib/modules/search/api.dart rename to lib/modules/search/core/api.dart diff --git a/lib/modules/search/api/api.dart b/lib/modules/search/core/api/api.dart similarity index 97% rename from lib/modules/search/api/api.dart rename to lib/modules/search/core/api/api.dart index 05071659..8586786e 100644 --- a/lib/modules/search/api/api.dart +++ b/lib/modules/search/core/api/api.dart @@ -2,7 +2,7 @@ import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:xml_parser/xml_parser.dart'; import 'package:lunasea/core.dart'; -import '../../search.dart'; +import 'package:lunasea/modules/search.dart'; class NewznabAPI extends API { final Map _values; @@ -19,7 +19,7 @@ class NewznabAPI extends API { 'User-Agent': Constants.USER_AGENT, }, queryParameters: { - 'apikey': indexer.key, + if(indexer.key != '') 'apikey': indexer.key, }, followRedirects: true, maxRedirects: 5, diff --git a/lib/modules/search/api/data.dart b/lib/modules/search/core/api/data.dart similarity index 100% rename from lib/modules/search/api/data.dart rename to lib/modules/search/core/api/data.dart diff --git a/lib/modules/search/api/data/category.dart b/lib/modules/search/core/api/data/category.dart similarity index 96% rename from lib/modules/search/api/data/category.dart rename to lib/modules/search/core/api/data/category.dart index 831ef117..159b5b96 100644 --- a/lib/modules/search/api/data/category.dart +++ b/lib/modules/search/core/api/data/category.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import '../../../search.dart'; +import 'package:lunasea/modules/search.dart'; class NewznabCategoryData { int id; diff --git a/lib/modules/search/api/data/result.dart b/lib/modules/search/core/api/data/result.dart similarity index 100% rename from lib/modules/search/api/data/result.dart rename to lib/modules/search/core/api/data/result.dart diff --git a/lib/modules/search/api/data/subcategory.dart b/lib/modules/search/core/api/data/subcategory.dart similarity index 100% rename from lib/modules/search/api/data/subcategory.dart rename to lib/modules/search/core/api/data/subcategory.dart diff --git a/lib/modules/search/core/constants.dart b/lib/modules/search/core/constants.dart new file mode 100644 index 00000000..ea05a2e1 --- /dev/null +++ b/lib/modules/search/core/constants.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; + +class SearchConstants { + SearchConstants._(); + + static const String MODULE_KEY = 'search'; + + static const Map MODULE_MAP = { + 'name': 'Search', + 'desc': 'Search Newznab Indexers', + 'icon': Icons.search, + 'route': '/search', + }; +} diff --git a/lib/modules/search/core/database.dart b/lib/modules/search/core/database.dart new file mode 100644 index 00000000..7a973355 --- /dev/null +++ b/lib/modules/search/core/database.dart @@ -0,0 +1,3 @@ +class SearchDatabase { + SearchDatabase._(); +} diff --git a/lib/core/ui/dialogs/search.dart b/lib/modules/search/core/dialogs.dart similarity index 64% rename from lib/core/ui/dialogs/search.dart rename to lib/modules/search/core/dialogs.dart index 1a383d3f..04148eae 100644 --- a/lib/core/ui/dialogs/search.dart +++ b/lib/modules/search/core/dialogs.dart @@ -1,21 +1,25 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; -class LSDialogSearch { - static Future downloadResult(BuildContext context) async { - bool flag = false; - String service = ''; +class SearchDialogs { + SearchDialogs._(); + + static Future> downloadResult(BuildContext context) async { + bool _flag = false; + String _service = ''; + + void _setValues(bool flag, String service) { + _flag = flag; + _service = service; + Navigator.of(context).pop(); + } + await showDialog( context: context, builder: (BuildContext context) => AlertDialog( - contentPadding: EdgeInsets.symmetric(horizontal: 24.0), title: LSDialog.title(text: 'Download'), actions: [ - LSDialog.button( - text: 'Cancel', - textColor: LSColors.accent, - onPressed: () => Navigator.of(context).pop(), - ), + LSDialog.cancel(context, textColor: LSColors.accent), ], content: ValueListenableBuilder( valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]), @@ -43,47 +47,34 @@ class LSDialogSearch { }, isExpanded: true, ), - padding: EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 16.0), + padding: EdgeInsets.fromLTRB(36.0, 0.0, 12.0, 16.0), ), if(Database.currentProfileObject.sabnzbdEnabled) LSDialog.tile( - icon: true, - iconData: CustomIcons.sabnzbd, + icon: CustomIcons.sabnzbd, iconColor: LSColors.list(0), text: 'SABnzbd', - onTap: () { - flag = true; - service = 'sabnzbd'; - Navigator.of(context).pop(); - } + onTap: () => _setValues(true, 'sabnzbd'), ), if(Database.currentProfileObject.nzbgetEnabled) LSDialog.tile( - icon: true, - iconData: CustomIcons.nzbget, + icon: CustomIcons.nzbget, iconColor: LSColors.list(1), text: 'NZBGet', - onTap: () { - flag = true; - service = 'nzbget'; - Navigator.of(context).pop(); - } + onTap: () => _setValues(true, 'nzbget'), ), LSDialog.tile( - icon: true, - iconData: Icons.file_download, + icon: Icons.file_download, iconColor: LSColors.list(2), text: 'Download to Device', - onTap: () { - flag = true; - service = 'filesystem'; - Navigator.of(context).pop(); - } + onTap: () => _setValues(true, 'filesystem'), ), ], ), ), ), + contentPadding: EdgeInsets.fromLTRB(0.0, 26.0, 24.0, 0.0), + shape: LSDialog.shape(), ), ); - return [flag, service]; + return [_flag, _service]; } -} \ No newline at end of file +} diff --git a/lib/modules/search/state.dart b/lib/modules/search/core/state.dart similarity index 96% rename from lib/modules/search/state.dart rename to lib/modules/search/core/state.dart index 3526ebfe..2d0c53ef 100644 --- a/lib/modules/search/state.dart +++ b/lib/modules/search/core/state.dart @@ -1,6 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:lunasea/core.dart'; -import '../search.dart'; +import 'package:lunasea/modules/search.dart'; class SearchModel extends ChangeNotifier { IndexerHiveObject _indexer; diff --git a/lib/modules/search/routes/details.dart b/lib/modules/search/routes/details.dart index 51a7b444..fe32dcc2 100644 --- a/lib/modules/search/routes/details.dart +++ b/lib/modules/search/routes/details.dart @@ -39,7 +39,7 @@ class _State extends State { title: LSTitle(text: 'Release Title'), subtitle: LSSubtitle(text: _state?.resultDetails?.title ?? 'Unknown'), trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: () => LSDialogSystem.textPreview(context, 'Release Title', _state?.resultDetails?.title ?? 'Unknown'), + onTap: () => GlobalDialogs.textPreview(context, 'Release Title', _state?.resultDetails?.title ?? 'Unknown'), ), LSContainerRow( children: [ diff --git a/lib/modules/search/routes/indexers.dart b/lib/modules/search/routes/indexers.dart index 3fa85db2..fd09b94b 100644 --- a/lib/modules/search/routes/indexers.dart +++ b/lib/modules/search/routes/indexers.dart @@ -13,15 +13,27 @@ class _State extends State { final _scaffoldKey = GlobalKey(); @override - Widget build(BuildContext context) => ValueListenableBuilder( - valueListenable: Database.indexersBox.listenable(), - builder: (context, indexerBox, widget) => Scaffold( - key: _scaffoldKey, - appBar: _appBar, - drawer: _drawer, - body: indexerBox.values.length > 0 - ? _body - : _nothing, + Widget build(BuildContext context) => WillPopScope( + onWillPop: () async { + if(_scaffoldKey.currentState.isDrawerOpen) { + //If the drawer is open, return true to close it + return true; + } else { + //If the drawer isn't open, open the drawer + _scaffoldKey.currentState.openDrawer(); + return false; + } + }, + child: ValueListenableBuilder( + valueListenable: Database.indexersBox.listenable(), + builder: (context, indexerBox, widget) => Scaffold( + key: _scaffoldKey, + appBar: _appBar, + drawer: _drawer, + body: indexerBox.values.length > 0 + ? _body + : _nothing, + ), ), ); diff --git a/lib/modules/search/widgets/download_button.dart b/lib/modules/search/widgets/download_button.dart index 6f8bd8c0..21204008 100644 --- a/lib/modules/search/widgets/download_button.dart +++ b/lib/modules/search/widgets/download_button.dart @@ -19,7 +19,7 @@ class SearchDetailsDownloadButton extends StatelessWidget { ); Future _sendToClient(BuildContext context) async { - List _values = await LSDialogSearch.downloadResult(context); + List _values = await SearchDialogs.downloadResult(context); if(_values[0]) { final _result = Provider.of(context, listen: false).resultDetails; switch(_values[1]) { diff --git a/lib/modules/settings.dart b/lib/modules/settings.dart index ff0e347b..a2e517e6 100644 --- a/lib/modules/settings.dart +++ b/lib/modules/settings.dart @@ -1,4 +1,3 @@ -export './settings/api.dart'; -export './settings/state.dart'; +export './settings/core.dart'; export './settings/routes.dart'; export './settings/widgets.dart'; diff --git a/lib/modules/settings/core.dart b/lib/modules/settings/core.dart new file mode 100644 index 00000000..1a5be69e --- /dev/null +++ b/lib/modules/settings/core.dart @@ -0,0 +1,5 @@ +export './core/api.dart'; +export './core/constants.dart'; +export './core/database.dart'; +export './core/dialogs.dart'; +export './core/state.dart'; diff --git a/lib/modules/settings/api.dart b/lib/modules/settings/core/api.dart similarity index 100% rename from lib/modules/settings/api.dart rename to lib/modules/settings/core/api.dart diff --git a/lib/modules/settings/api/api.dart b/lib/modules/settings/core/api/api.dart similarity index 100% rename from lib/modules/settings/api/api.dart rename to lib/modules/settings/core/api/api.dart diff --git a/lib/modules/settings/core/constants.dart b/lib/modules/settings/core/constants.dart new file mode 100644 index 00000000..566450bf --- /dev/null +++ b/lib/modules/settings/core/constants.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; + +class SettingsConstants { + SettingsConstants._(); + + static const String MODULE_KEY = 'settings'; + + static const Map MODULE_MAP = { + 'name': 'Settings', + 'desc': 'Update Configuration', + 'icon': Icons.settings, + 'route': '/settings', + }; +} diff --git a/lib/modules/settings/core/database.dart b/lib/modules/settings/core/database.dart new file mode 100644 index 00000000..a5559f54 --- /dev/null +++ b/lib/modules/settings/core/database.dart @@ -0,0 +1,3 @@ +class SettingsDatabase { + SettingsDatabase._(); +} diff --git a/lib/modules/settings/core/dialogs.dart b/lib/modules/settings/core/dialogs.dart new file mode 100644 index 00000000..dbade092 --- /dev/null +++ b/lib/modules/settings/core/dialogs.dart @@ -0,0 +1,850 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules.dart'; + +class SettingsDialogs { + SettingsDialogs._(); + + static Future> editHost(BuildContext context, String title, { String prefill = '' }) async { + bool _flag = false; + final _formKey = GlobalKey(); + final _textController = TextEditingController()..text = prefill; + + void _setValues(bool flag) { + if(_formKey.currentState.validate()) { + _flag = flag; + Navigator.of(context).pop(); + } + } + + await LSDialog.dialog( + context: context, + title: title, + buttons: [ + LSDialog.button( + text: 'Save', + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.richText( + children: [ + LSDialog.textSpanContent(text: '•\tThis is the URL in which you access the web GUI for the service\n'), + LSDialog.textSpanContent(text: '•\tYou must include either '), + LSDialog.bolded(text: 'http://'), + LSDialog.textSpanContent(text: ' or '), + LSDialog.bolded(text: 'https://\n'), + LSDialog.textSpanContent(text: '•\tWhen not using a reverse proxy, please include the port: '), + LSDialog.bolded(text: 'url:port\n'), + LSDialog.textSpanContent(text: '•\tTo add basic authentication, please set a custom header in the advanced section'), + ], + ), + Form( + key: _formKey, + child: LSDialog.textFormInput( + controller: _textController, + title: title, + keyboardType: TextInputType.url, + onSubmitted: (_) => _setValues(true), + validator: (_) => null, + ), + ), + ], + contentPadding: LSDialog.inputTextDialogContentPadding(), + ); + return [_flag, _textController.text]; + } + + static Future> deleteIndexer(BuildContext context) async { + bool _flag = false; + + void _setValues(bool flag) { + _flag = flag; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Delete Indexer', + buttons: [ + LSDialog.button( + text: 'Delete', + textColor: Colors.red, + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.textContent( + text: 'Are you sure you want to delete this indexer?' + ), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag]; + } + + static Future> deleteHeader(BuildContext context) async { + bool _flag = false; + + void _setValues(bool flag) { + _flag = flag; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Delete Header', + buttons: [ + LSDialog.button( + text: 'Delete', + textColor: Colors.red, + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.textContent( + text: 'Are you sure you want to delete this header?' + ), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag]; + } + + static Future> addHeader(BuildContext context) async { + List> _options = [ + ['Basic Authentication', Icons.verified_user, 1], + ['Custom...', Icons.device_hub, 100], + ]; + bool _flag = false; + int _type = -1; + + void _setValues(bool flag, int type) { + _flag = flag; + _type = type; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Add Header', + content: List.generate( + _options.length, + (index) => LSDialog.tile( + text: _options[index][0], + icon: _options[index][1], + iconColor: LSColors.list(index), + onTap: () => _setValues(true, _options[index][2]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _type]; + } + + static Future> addCustomHeader(BuildContext context) async { + bool _flag = false; + final formKey = GlobalKey(); + TextEditingController _key = TextEditingController(); + TextEditingController _value = TextEditingController(); + + void _setValues(bool flag) { + if(formKey.currentState.validate()) { + _flag = flag; + Navigator.of(context).pop(); + } + } + + await LSDialog.dialog( + context: context, + title: 'Custom Header', + buttons: [ + LSDialog.button( + text: 'Add', + onPressed: () => _setValues(true), + ), + ], + content: [ + Form( + key: formKey, + child: Column( + children: [ + LSDialog.textFormInput( + controller: _key, + validator: (key) => key.length > 0 ? null : 'Key Required', + onSubmitted: (_) => _setValues(true), + title: 'Header Key', + ), + LSDialog.textFormInput( + controller: _value, + validator: (value) => value.length > 0 ? null : 'Value Required', + onSubmitted: (_) => _setValues(true), + title: 'Header Value', + ), + ], + ), + ), + ], + contentPadding: LSDialog.inputDialogContentPadding(), + ); + return [_flag, _key.text, _value.text]; + } + + static Future> addAuthenticationHeader(BuildContext context) async { + bool _flag = false; + final _formKey = GlobalKey(); + final _username = TextEditingController(); + final _password = TextEditingController(); + + void _setValues(bool flag) { + if(_formKey.currentState.validate()) { + _flag = flag; + Navigator.of(context).pop(); + } + } + + await LSDialog.dialog( + context: context, + title: 'Basic Authentication', + buttons: [ + LSDialog.button( + text: 'Add', + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.richText( + children: [ + LSDialog.textSpanContent(text: '•\tThe username '), + LSDialog.bolded(text: 'cannot'), + LSDialog.textSpanContent(text: ' contain a colon\n'), + LSDialog.textSpanContent(text: '•\tThe password '), + LSDialog.bolded(text: 'can'), + LSDialog.textSpanContent(text: ' contain a colon\n'), + LSDialog.textSpanContent(text: '•\tThe username and password are automatically converted to base64 encoding\n'), + ] + ), + Form( + key: _formKey, + child: Column( + children: [ + LSDialog.textFormInput( + controller: _username, + validator: (username) => username.length > 0 ? null : 'Username Required', + onSubmitted: (_) => _setValues(true), + title: 'Username', + ), + LSDialog.textFormInput( + controller: _password, + validator: (password) => password.length > 0 ? null : 'Password Required', + onSubmitted: (_) => _setValues(true), + obscureText: true, + title: 'Password', + ), + ], + ), + ), + ], + contentPadding: LSDialog.inputTextDialogContentPadding(), + ); + return [_flag, _username.text, _password.text]; + } + + static Future> exportLogs(BuildContext context) async { + bool _flag = false; + + void _setValues(bool flag) { + _flag = flag; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Export Logs', + buttons: [ + LSDialog.button( + text: 'Export', + onPressed: () => _setValues(true), + textColor: LSColors.accent, + ), + ], + content: [ + LSDialog.textContent(text: 'Are you sure you want to export all recorded logs to the filesystem?\n'), + LSDialog.richText( + children: [ + if(Platform.isIOS || Platform.isAndroid) LSDialog.textSpanContent(text: 'The exported logs can be found in '), + if(Platform.isIOS) LSDialog.bolded(text: '/LunaSea/FLogs'), + if(Platform.isAndroid) LSDialog.bolded(text: '/Android/data/app.lunasea.lunasea/files/FLogs'), + LSDialog.textSpanContent(text: '.'), + ], + alignment: TextAlign.center, + ), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag]; + } + + static Future> clearLogs(BuildContext context) async { + bool _flag = false; + + void _setValues(bool flag) { + _flag = flag; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Clear Logs', + buttons: [ + LSDialog.button( + text: 'Clear', + onPressed: () => _setValues(true), + textColor: LSColors.red, + ), + ], + content: [ + LSDialog.textContent(text: 'Are you sure you want to clear all recorded logs?\n'), + LSDialog.textContent(text: 'Logs can be useful for bug reports and debugging.'), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag]; + } + + static Future> toggleStrictTLS(BuildContext context) async { + bool _flag = false; + + void _setValues(bool flag) { + _flag = flag; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Disable Strict SSL/TLS Validation', + buttons: [ + LSDialog.button( + text: 'Disable', + onPressed: () => _setValues(true), + textColor: LSColors.red, + ), + ], + content: [ + LSDialog.richText( + children: [ + LSDialog.bolded( + text: 'Please do not modify this setting unless you know what you are doing.\n\n', + color: LSColors.red, + fontSize: 12.0, + ), + LSDialog.textSpanContent(text: 'Are you sure you want to disable strict SSL/TLS validation?\n\n'), + LSDialog.textSpanContent(text: 'Disabling strict SSL/TLS validation means that LunaSea will not validate the host machine\'s SSL certificate against a certificate authority.\n\n'), + LSDialog.textSpanContent(text: 'LunaSea will still connect to your host machine securely when using SSL/TLS whether strict SSL/TLS validation is enabled or disabled.\n\n'), + LSDialog.bolded( + text: 'Warning: Disabling strict SSL/TLS for an invalid or self-signed certificate will prevent a large amount of images from loading within LunaSea.', + color: LSColors.red, + fontSize: 12.0, + ), + ], + alignment: TextAlign.center, + ), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag]; + } + + static Future> nzbgetBasicAuthentication(BuildContext context) async { + bool _flag = false; + + void _setValues(bool flag) { + _flag = flag; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Use Basic Authentication', + buttons: [ + LSDialog.button( + text: 'Use', + onPressed: () => _setValues(true), + textColor: LSColors.red, + ), + ], + content: [ + LSDialog.richText( + children: [ + LSDialog.bolded( + text: 'Please do not modify this setting unless you know what you are doing.\n\n', + color: LSColors.red, + fontSize: 12.0, + ), + LSDialog.textSpanContent(text: 'Are you sure you want to use basic authentication to connect to NZBGet?\n\n'), + LSDialog.textSpanContent(text: 'Basic authentication will add your username and password as a header in the request instead of encoding the details into the URL.\n\n'), + LSDialog.bolded( + text: 'Warning: This will allow you to have more complex passwords, but interfere with layered authentication methods.', + color: LSColors.red, + fontSize: 12.0, + ), + ], + alignment: TextAlign.center, + ), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag]; + } + + static Future> addProfile(BuildContext context) async { + final _formKey = GlobalKey(); + final _controller = TextEditingController(); + bool _flag = false; + + void _setValues(bool flag) { + if(_formKey.currentState.validate()) { + _flag = flag; + Navigator.of(context).pop(); + } + } + + await LSDialog.dialog( + context: context, + title: 'Add Profile', + buttons: [ + LSDialog.button( + text: 'Save', + onPressed: () => _setValues(true), + ), + ], + content: [ + Form( + key: _formKey, + child: LSDialog.textFormInput( + controller: _controller, + validator: (value) => value.length > 0 ? null : 'Name Required', + onSubmitted: (_) => _setValues(true), + title: 'Profile Name', + ), + ), + ], + contentPadding: LSDialog.inputDialogContentPadding(), + ); + return [_flag, _controller.text]; + } + + static Future> renameProfile(BuildContext context, List profiles) async { + bool _flag = false; + String _profile = ''; + + void _setValues(bool flag, String profile) { + _flag = flag; + _profile = profile; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Rename Profile', + content: List.generate( + profiles.length, + (index) => LSDialog.tile( + icon: Icons.settings, + iconColor: LSColors.list(index), + text: profiles[index], + onTap: () => _setValues(true, profiles[index]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _profile]; + } + + static Future> renameProfileSelected(BuildContext context) async { + final _formKey = GlobalKey(); + final _controller = TextEditingController(); + bool _flag = false; + + void _setValues(bool flag) { + if(_formKey.currentState.validate()) { + _flag = flag; + Navigator.of(context).pop(); + } + } + + await LSDialog.dialog( + context: context, + title: 'Rename Profile', + buttons: [ + LSDialog.button(text: 'Rename', onPressed: () => _setValues(true), textColor: LSColors.accent), + ], + content: [ + Form( + key: _formKey, + child:LSDialog.textFormInput( + controller: _controller, + validator: (value) => value.length > 0 ? null : 'Name Required', + onSubmitted: (_) => _setValues(true), + title: 'New Profile Name', + ), + ), + ], + contentPadding: LSDialog.inputDialogContentPadding(), + ); + return [_flag, _controller.text]; + } + + static Future> deleteProfile(BuildContext context, List profiles) async { + bool _flag = false; + String _profile = ''; + + void _setValues(bool flag, String profile) { + _flag = flag; + _profile = profile; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Delete Profile', + content: List.generate( + profiles.length, + (index) => LSDialog.tile( + icon: Icons.settings, + iconColor: LSColors.list(index), + text: profiles[index], + onTap: () => _setValues(true, profiles[index]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _profile]; + } + + static Future> enabledProfile(BuildContext context, List profiles) async { + bool _flag = false; + String _profile = ''; + + void _setValues(bool flag, String profile) { + _flag = flag; + _profile = profile; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Enabled Profile', + content: List.generate( + profiles.length, + (index) => LSDialog.tile( + icon: Icons.settings, + iconColor: LSColors.list(index), + text: profiles[index], + onTap: () => _setValues(true, profiles[index]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _profile]; + } + + static Future> changeBrowser(BuildContext context) async { + bool _flag = false; + LSBrowsers _browser; + + void _setValues(bool flag, LSBrowsers browser) { + _flag = flag; + _browser = browser; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Open Links In...', + content: List.generate( + LSBrowsers.values.length, + (index) => LSDialog.tile( + icon: LSBrowsers.values[index].icon, + iconColor: LSColors.list(index), + text: LSBrowsers.values[index].name, + onTap: () => _setValues(true, LSBrowsers.values[index]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _browser]; + } + + static Future> editCalendarStartingDay(BuildContext context) async { + bool _flag = false; + CalendarStartingDay _startingDate; + + void _setValues(bool flag, CalendarStartingDay startingDate) { + _flag = flag; + _startingDate = startingDate; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Starting Day of Week', + content: List.generate( + CalendarStartingDay.values.length, + (index) => LSDialog.tile( + icon: CustomIcons.calendar, + iconColor: LSColors.list(index), + text: CalendarStartingDay.values[index].name, + onTap: () => _setValues(true, CalendarStartingDay.values[index]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _startingDate]; + } + + static Future> editCalendarStartingSize(BuildContext context) async { + bool _flag = false; + CalendarStartingSize _startingSize; + + void _setValues(bool flag, CalendarStartingSize startingSize) { + _flag = flag; + _startingSize = startingSize; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Starting Size', + content: List.generate( + CalendarStartingSize.values.length, + (index) => LSDialog.tile( + icon: CalendarStartingSize.values[index].icon, + iconColor: LSColors.list(index), + text: CalendarStartingSize.values[index].name, + onTap: () => _setValues(true, CalendarStartingSize.values[index]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _startingSize]; + } + + static Future> editBroadcastAddress(BuildContext context, String prefill) async { + bool _flag = false; + final _formKey = GlobalKey(); + final _controller = TextEditingController()..text = prefill; + + void _setValues(bool flag) { + if(_formKey.currentState.validate()) { + _flag = flag; + Navigator.of(context).pop(); + } + } + + await LSDialog.dialog( + context: context, + title: 'Broadcast Address', + buttons: [ + LSDialog.button( + text: 'Save', + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.richText( + children: [ + LSDialog.textSpanContent(text: '•\tThis is the broadcast address of your local network\n'), + LSDialog.textSpanContent(text: '•\tTypically this is the IP address of your machine with the last octet set to 255\n'), + LSDialog.textSpanContent(text: '•\tGiven an example machine IP address of 192.168.1.111, the resulting broadcast IP address is '), + LSDialog.bolded(text: '192.168.1.255'), + ], + ), + Form( + key: _formKey, + child: LSDialog.textFormInput( + controller: _controller, + validator: (address) => IPv4Address.validate(address) + ? null + : 'Invalid Broadcast Address', + onSubmitted: (_) => _setValues(true), + title: 'Broadcast Address', + ), + ), + ], + contentPadding: LSDialog.inputTextDialogContentPadding(), + ); + return [_flag, _controller.text]; + } + + static Future> editMACAddress(BuildContext context, String prefill) async { + bool _flag = false; + final formKey = GlobalKey(); + final _controller = TextEditingController()..text = prefill; + + void _setValues(bool flag) { + if(formKey.currentState.validate()) { + _flag = flag; + Navigator.of(context).pop(); + } + } + + await LSDialog.dialog( + context: context, + title: 'MAC Address', + buttons: [ + LSDialog.button( + text: 'Save', + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.richText( + children: [ + LSDialog.textSpanContent(text: '•\tThis is the MAC address of the machine that you want to wake up\n'), + LSDialog.textSpanContent(text: '•\tMAC addresses contain six two-digit hexidecimal nibbles (an octet)\n'), + LSDialog.textSpanContent(text: '•\tHexidecimal digits range from 0-9 and A-F\n'), + LSDialog.textSpanContent(text: '•\tEach hexidecimal octet should be separated by a colon\n'), + LSDialog.textSpanContent(text: '•\tExample: '), + LSDialog.bolded(text: 'A4:83:E7:0D:7F:4F'), + ], + ), + Form( + key: formKey, + child: LSDialog.textFormInput( + controller: _controller, + validator: (address) => MacAddress.validate(address) + ? null + : 'Invalid MAC Address', + onSubmitted: (_) => _setValues(true), + title: 'MAC Address', + ), + ), + ], + contentPadding: LSDialog.inputTextDialogContentPadding(), + ); + return [_flag, _controller.text]; + } + + static Future> clearLunaSeaConfiguration(BuildContext context) async { + bool _flag = false; + + void _setValues(bool flag) { + _flag = flag; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Reset LunaSea', + buttons: [ + LSDialog.button( + text: 'Reset', + textColor: LSColors.red, + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.textContent(text: 'Are you sure you want to reset LunaSea and clear your configuration?\n'), + LSDialog.textContent(text: 'You will be starting from a clean slate, please ensure you backup your current configuration first!'), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag]; + } + + static Future> enterEncryptionKey(BuildContext context) async { + bool _flag = false; + final _formKey = GlobalKey(); + final _textController = TextEditingController(); + + void _setValues(bool flag) { + if(_formKey.currentState.validate()) { + _flag = flag; + Navigator.of(context).pop(); + } + } + + await LSDialog.dialog( + context: context, + title: 'Decrypt Backup', + buttons: [ + LSDialog.button( + text: 'Restore', + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.textContent(text: 'Please enter the encryption key for this backup.'), + Form( + key: _formKey, + child: LSDialog.textFormInput( + controller: _textController, + title: 'Encryption Key', + obscureText: true, + onSubmitted: (_) => _setValues(true), + validator: (value) => value.length < 8 + ? 'Minimum of 8 characters' + : null, + ), + ), + ], + contentPadding: LSDialog.inputTextDialogContentPadding(), + ); + return [_flag, _textController.text]; + } + + static Future> backupConfiguration(BuildContext context) async { + bool _flag = false; + final _formKey = GlobalKey(); + final _textController = TextEditingController(); + + void _setValues(bool flag) { + if(_formKey.currentState.validate()) { + _flag = flag; + Navigator.of(context).pop(); + } + } + + await LSDialog.dialog( + context: context, + title: 'Backup Configuration', + buttons: [ + LSDialog.button( + text: 'Backup', + textColor: LSColors.accent, + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.richText( + children: [ + LSDialog.textSpanContent(text: '•\tAll backups are encrypted before being exported to the filesystem\n'), + LSDialog.textSpanContent(text: '•\tThe encryption key must be at least 8 characters\n'), + LSDialog.textSpanContent(text: '•\tBackups can be found in '), + if(Platform.isIOS) LSDialog.bolded(text: '/LunaSea/configurations'), + if(Platform.isAndroid) LSDialog.bolded(text: '/Android/data/app.lunasea.lunasea/files/configurations'), + ], + ), + Form( + key: _formKey, + child: LSDialog.textFormInput( + obscureText: true, + controller: _textController, + title: 'Encryption Key', + validator: (value) { + if(value.length < 8) { + return 'Minimum of 8 characters'; + } + return null; + }, + onSubmitted: (_) => _setValues(true), + ), + ), + ], + contentPadding: LSDialog.inputTextDialogContentPadding(), + ); + return [_flag, _textController.text]; + } +} diff --git a/lib/modules/settings/state.dart b/lib/modules/settings/core/state.dart similarity index 100% rename from lib/modules/settings/state.dart rename to lib/modules/settings/core/state.dart diff --git a/lib/modules/settings/routes.dart b/lib/modules/settings/routes.dart index a17e99ad..2e5118f8 100644 --- a/lib/modules/settings/routes.dart +++ b/lib/modules/settings/routes.dart @@ -13,6 +13,7 @@ export './routes/modules/general_lunasea.dart'; export './routes/modules/general_search.dart'; export './routes/modules/general_search_add.dart'; export './routes/modules/general_search_edit.dart'; +export './routes/modules/general_wake_on_lan.dart'; // Logs export './routes/logs/logs_details.dart'; export './routes/logs/logs_types.dart'; @@ -20,3 +21,5 @@ export './routes/logs/logs_view.dart'; // System export './routes/system/resources_donations.dart'; export './routes/system/resources_donations_thankyou.dart'; +export './routes/system/changelog.dart'; +export './routes/system/changelog_details.dart'; diff --git a/lib/modules/settings/routes/logs/logs_details.dart b/lib/modules/settings/routes/logs/logs_details.dart index 6d454b9e..64586275 100644 --- a/lib/modules/settings/routes/logs/logs_details.dart +++ b/lib/modules/settings/routes/logs/logs_details.dart @@ -57,19 +57,19 @@ class _State extends State { title: LSTitle(text: 'Message'), subtitle: LSSubtitle(text: _arguments?.log?.text ?? 'Unknown', maxLines: 1), trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: () async => LSDialogSystem.textPreview(context, 'Message', _arguments?.log?.text), + onTap: () async => GlobalDialogs.textPreview(context, 'Message', _arguments?.log?.text), ), if(_arguments?.log?.exception != 'null') LSCardTile( title: LSTitle(text: 'Exception'), subtitle: LSSubtitle(text: _arguments?.log?.exception ?? 'Unknown', maxLines: 1), trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: () async => LSDialogSystem.textPreview(context, 'Exception', _arguments?.log?.exception, alignLeft: true), + onTap: () async => GlobalDialogs.textPreview(context, 'Exception', _arguments?.log?.exception, alignLeft: true), ), if(_arguments?.log?.stacktrace != 'null') LSCardTile( title: LSTitle(text: 'Stack Trace'), subtitle: LSSubtitle(text: _arguments?.log?.stacktrace ?? 'Unknown', maxLines: 1), trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: () async => LSDialogSystem.textPreview(context, 'Stack Trace', _arguments?.log?.stacktrace, alignLeft: true), + onTap: () async => GlobalDialogs.textPreview(context, 'Stack Trace', _arguments?.log?.stacktrace, alignLeft: true), ), ], padBottom: true, diff --git a/lib/modules/settings/routes/modules.dart b/lib/modules/settings/routes/modules.dart index bc43f3e4..4c76eb4c 100644 --- a/lib/modules/settings/routes/modules.dart +++ b/lib/modules/settings/routes/modules.dart @@ -65,6 +65,16 @@ class _State extends State with AutomaticKeepAliveClientMixin { ), onTap: () async => Navigator.of(context).pushNamed(SettingsModulesSearch.ROUTE_NAME), ), + if(ModuleFlags.WAKE_ON_LAN) LSCardTile( + title: LSTitle(text: Constants.MODULE_MAP['wake_on_lan']['name']), + subtitle: LSSubtitle(text: 'Configure Wake on LAN'), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + leading: LSIconButton( + icon: Constants.MODULE_MAP['wake_on_lan']['icon'], + color: LSColors.list(3), + ), + onTap: () async => Navigator.of(context).pushNamed(SettingsModulesWakeOnLAN.ROUTE_NAME), + ), ]; List get _automation => [ @@ -78,7 +88,7 @@ class _State extends State with AutomaticKeepAliveClientMixin { trailing: LSIconButton(icon: Icons.arrow_forward_ios), leading: LSIconButton( icon: CustomIcons.music, - color: LSColors.list(3), + color: LSColors.list(4), ), onTap: () async => Navigator.of(context).pushNamed(SettingsModulesLidarr.ROUTE_NAME), ), @@ -88,7 +98,7 @@ class _State extends State with AutomaticKeepAliveClientMixin { trailing: LSIconButton(icon: Icons.arrow_forward_ios), leading: LSIconButton( icon: CustomIcons.movies, - color: LSColors.list(4), + color: LSColors.list(5), ), onTap: () async => Navigator.of(context).pushNamed(SettingsModulesRadarr.ROUTE_NAME), ), @@ -98,7 +108,7 @@ class _State extends State with AutomaticKeepAliveClientMixin { trailing: LSIconButton(icon: Icons.arrow_forward_ios), leading: LSIconButton( icon: CustomIcons.television, - color: LSColors.list(5), + color: LSColors.list(6), ), onTap: () async => Navigator.of(context).pushNamed(SettingsModulesSonarr.ROUTE_NAME), ), @@ -115,7 +125,7 @@ class _State extends State with AutomaticKeepAliveClientMixin { trailing: LSIconButton(icon: Icons.arrow_forward_ios), leading: LSIconButton( icon: CustomIcons.nzbget, - color: LSColors.list(6), + color: LSColors.list(7), ), onTap: () async => Navigator.of(context).pushNamed(SettingsModulesNZBGet.ROUTE_NAME), ), @@ -125,7 +135,7 @@ class _State extends State with AutomaticKeepAliveClientMixin { trailing: LSIconButton(icon: Icons.arrow_forward_ios), leading: LSIconButton( icon: CustomIcons.sabnzbd, - color: LSColors.list(7), + color: LSColors.list(8), ), onTap: () async => Navigator.of(context).pushNamed(SettingsModulesSABnzbd.ROUTE_NAME), ), @@ -142,7 +152,7 @@ class _State extends State with AutomaticKeepAliveClientMixin { trailing: LSIconButton(icon: Icons.arrow_forward_ios), leading: LSIconButton( icon: CustomIcons.monitoring, - color: LSColors.list(8), + color: LSColors.list(9), ), //onTap: () async => Navigator.of(context).pushNamed(SettingsModulesTautulli.ROUTE_NAME), ), diff --git a/lib/modules/settings/routes/modules/automation_lidarr.dart b/lib/modules/settings/routes/modules/automation_lidarr.dart index ed4eb20e..4ecc008f 100644 --- a/lib/modules/settings/routes/modules/automation_lidarr.dart +++ b/lib/modules/settings/routes/modules/automation_lidarr.dart @@ -1,117 +1,2 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/lidarr.dart'; - -class SettingsModulesLidarr extends StatefulWidget { - static const ROUTE_NAME = '/settings/modules/lidarr'; - - @override - State createState() => _State(); -} - -class _State extends State { - final _scaffoldKey = GlobalKey(); - ProfileHiveObject _profile = Database.currentProfileObject; - - @override - Widget build(BuildContext context) => Scaffold( - key: _scaffoldKey, - body: _body, - appBar: _appBar, - ); - - Widget get _appBar => LSAppBar(title: 'Lidarr'); - - Widget get _body => ValueListenableBuilder( - valueListenable: Database.profilesBox.listenable(), - builder: (context, box, widget) { - return LSListView( - children: [ - LSHeader( - text: 'Configuration', - subtitle: 'Mandatory configuration for Lidarr functionality', - ), - LSCardTile( - title: LSTitle(text: 'Enable Lidarr'), - subtitle: null, - trailing: Switch( - value: _profile.lidarrEnabled ?? false, - onChanged: (value) { - _profile.lidarrEnabled = value; - _profile.save(); - }, - ), - ), - LSCardTile( - title: LSTitle(text: 'Host'), - subtitle: LSSubtitle( - text: _profile.lidarrHost == null || _profile.lidarrHost == '' - ? 'Not Set' - : _profile.lidarrHost - ), - trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: _changeHost, - ), - LSCardTile( - title: LSTitle(text: 'API Key'), - subtitle: LSSubtitle( - text: _profile.lidarrKey == null || _profile.lidarrKey == '' - ? 'Not Set' - : '••••••••••••' - ), - trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: _changeKey, - ), - LSDivider(), - LSButton( - text: 'Test Connection', - onTap: _testConnection, - ), - LSHeader( - text: 'Advanced', - subtitle: 'Advanced options for users with non-standard networking configurations. Be careful!', - ), - LSCardTile( - title: LSTitle(text: 'Strict SSL/TLS Validation'), - subtitle: LSSubtitle(text: 'For Invalid Certificates'), - trailing: Switch( - value: _profile.lidarrStrictTLS ?? true, - onChanged: (value) async { - if(value) { - _profile.lidarrStrictTLS = value; - _profile.save(); - } else { - List _values = await LSDialogSettings.toggleStrictTLS(context); - if(_values[0]) { - _profile.lidarrStrictTLS = value; - _profile.save(); - } - } - }, - ), - ), - ], - ); - }, - ); - - Future _changeHost() async { - List _values = await LSDialogSystem.editText(context, 'Lidarr Host', prefill: _profile.lidarrHost ?? '', showHostHint: true); - if(_values[0]) { - _profile.lidarrHost = _values[1]; - _profile.save(); - } - } - - Future _changeKey() async { - List _values = await LSDialogSystem.editText(context, 'Lidarr API Key', prefill: _profile.lidarrKey ?? ''); - if(_values[0]) { - _profile.lidarrKey = _values[1]; - _profile.save(); - } - } - - Future _testConnection() async => await LidarrAPI.from(_profile).testConnection() - ? LSSnackBar(context: context, title: 'Connected Successfully', message: 'Lidarr is ready to use with LunaSea', type: SNACKBAR_TYPE.success) - : LSSnackBar(context: context, title: 'Connection Test Failed', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure); -} +export './automation_lidarr/automation_lidarr.dart'; +export './automation_lidarr/automation_lidarr_headers.dart'; diff --git a/lib/modules/settings/routes/modules/automation_lidarr/automation_lidarr.dart b/lib/modules/settings/routes/modules/automation_lidarr/automation_lidarr.dart new file mode 100644 index 00000000..a6762390 --- /dev/null +++ b/lib/modules/settings/routes/modules/automation_lidarr/automation_lidarr.dart @@ -0,0 +1,132 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/lidarr.dart'; +import 'package:lunasea/modules/settings.dart'; + +class SettingsModulesLidarr extends StatefulWidget { + static const ROUTE_NAME = '/settings/modules/lidarr'; + + @override + State createState() => _State(); +} + +class _State extends State { + final _scaffoldKey = GlobalKey(); + ProfileHiveObject _profile = Database.currentProfileObject; + + @override + Widget build(BuildContext context) => Scaffold( + key: _scaffoldKey, + body: _body, + appBar: _appBar, + ); + + Widget get _appBar => LSAppBar(title: 'Lidarr'); + + List get _configuration => [ + LSHeader( + text: 'Configuration', + subtitle: 'Mandatory configuration for Lidarr functionality', + ), + LSCardTile( + title: LSTitle(text: 'Enable Lidarr'), + subtitle: null, + trailing: Switch( + value: _profile.lidarrEnabled ?? false, + onChanged: (value) { + _profile.lidarrEnabled = value; + _profile.save(); + }, + ), + ), + LSCardTile( + title: LSTitle(text: 'Host'), + subtitle: LSSubtitle( + text: _profile.lidarrHost == null || _profile.lidarrHost == '' + ? 'Not Set' + : _profile.lidarrHost + ), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: _changeHost, + ), + LSCardTile( + title: LSTitle(text: 'API Key'), + subtitle: LSSubtitle( + text: _profile.lidarrKey == null || _profile.lidarrKey == '' + ? 'Not Set' + : '••••••••••••' + ), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: _changeKey, + ), + LSDivider(), + LSButton( + text: 'Test Connection', + onTap: _testConnection, + ), + ]; + + List get _advanced => [ + LSHeader( + text: 'Advanced', + subtitle: 'Advanced options for users with non-standard networking configurations. Be careful!', + ), + LSCardTile( + title: LSTitle(text: 'Custom Headers'), + subtitle: LSSubtitle(text: 'Add Custom Headers to Requests'), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: () async => Navigator.of(context).pushNamed(SettingsModulesLidarrHeaders.ROUTE_NAME), + ), + LSCardTile( + title: LSTitle(text: 'Strict SSL/TLS Validation'), + subtitle: LSSubtitle(text: 'For Invalid Certificates'), + trailing: Switch( + value: _profile.lidarrStrictTLS ?? true, + onChanged: (value) async { + if(value) { + _profile.lidarrStrictTLS = value; + _profile.save(); + } else { + List _values = await SettingsDialogs.toggleStrictTLS(context); + if(_values[0]) { + _profile.lidarrStrictTLS = value; + _profile.save(); + } + } + }, + ), + ), + ]; + + Widget get _body => ValueListenableBuilder( + valueListenable: Database.profilesBox.listenable(), + builder: (context, box, widget) { + return LSListView( + children: [ + ..._configuration, + ..._advanced, + ], + ); + }, + ); + + Future _changeHost() async { + List _values = await SettingsDialogs.editHost(context, 'Lidarr Host', prefill: _profile.lidarrHost ?? ''); + if(_values[0]) { + _profile.lidarrHost = _values[1]; + _profile.save(); + } + } + + Future _changeKey() async { + List _values = await GlobalDialogs.editText(context, 'Lidarr API Key', prefill: _profile.lidarrKey ?? ''); + if(_values[0]) { + _profile.lidarrKey = _values[1]; + _profile.save(); + } + } + + Future _testConnection() async => await LidarrAPI.from(_profile).testConnection() + ? LSSnackBar(context: context, title: 'Connected Successfully', message: 'Lidarr is ready to use with LunaSea', type: SNACKBAR_TYPE.success) + : LSSnackBar(context: context, title: 'Connection Test Failed', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure); +} diff --git a/lib/modules/settings/routes/modules/automation_lidarr/automation_lidarr_headers.dart b/lib/modules/settings/routes/modules/automation_lidarr/automation_lidarr_headers.dart new file mode 100644 index 00000000..67bb2d4c --- /dev/null +++ b/lib/modules/settings/routes/modules/automation_lidarr/automation_lidarr_headers.dart @@ -0,0 +1,110 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/settings.dart'; + +class SettingsModulesLidarrHeaders extends StatefulWidget { + static const ROUTE_NAME = '/settings/modules/lidarr/headers'; + + @override + State createState() => _State(); +} + +class _State extends State { + ProfileHiveObject _profile = Database.currentProfileObject; + + @override + Widget build(BuildContext context) => Scaffold( + appBar: _appBar, + body: _body, + ); + + Widget get _appBar => LSAppBar(title: 'Custom Headers'); + + Widget get _body => ValueListenableBuilder( + valueListenable: Database.profilesBox.listenable(), + builder: (context, profile, widget) => LSListView( + children: [ + ..._headers, + ], + ), + ); + + List get _headers => [ + LSHeader( + text: 'Custom Headers', + subtitle: 'Define custom headers that will be attached to every request made to the module.', + ), + if((_profile.getLidarr()['headers'] as Map).isEmpty) LSGenericMessage( + text: 'No Custom Headers Added', + ), + ..._list, + LSDivider(), + LSButton( + text: 'Add Header', + onTap: () async => _showAddPrompt(), + ), + ]; + + List get _list { + Map headers = Map.from(_profile.getLidarr()['headers']); + List list = []; + headers.forEach((key, value) => list.add(LSCardTile( + title: LSTitle(text: key.toString()), + subtitle: LSSubtitle(text: value.toString()), + trailing: LSIconButton( + icon: Icons.delete, + color: LSColors.red, + onPressed: () async => _deleteIndexer(key.toString(), value.toString()), + ), + ))); + list.sort((a,b) => (a.title as LSTitle).text.compareTo((b.title as LSTitle).text)); + return list; + } + + Future _showAddPrompt() async { + List results = await SettingsDialogs.addHeader(context); + if(results[0]) switch(results[1]) { + case 1: _showAuthenticationPrompt(); break; + case 100: _showCustomPrompt(); break; + default: break; + } + } + + Future _showAuthenticationPrompt() async { + List results = await SettingsDialogs.addAuthenticationHeader(context); + if(results[0]) { + Map _headers = Map.from(_profile.getLidarr()['headers']); + String _auth = base64.encode(utf8.encode('${results[1]}:${results[2]}')); + _headers.addAll({'Authorization': 'Basic $_auth'}); + _profile.lidarrHeaders = _headers; + _profile.save(); + } + } + + Future _showCustomPrompt() async { + List results = await SettingsDialogs.addCustomHeader(context); + if(results[0]) { + Map _headers = Map.from(_profile.getLidarr()['headers']); + _headers.addAll({results[1]: results[2]}); + _profile.lidarrHeaders = _headers; + _profile.save(); + } + } + + Future _deleteIndexer(String key, String value) async { + List results = await SettingsDialogs.deleteHeader(context); + if(results[0]) { + Map _headers = Map.from(_profile.getLidarr()['headers']); + _headers.remove(key); + _profile.lidarrHeaders = _headers; + _profile.save(); + LSSnackBar( + context: context, + message: key, + title: 'Header Deleted', + type: SNACKBAR_TYPE.success, + ); + } + } +} diff --git a/lib/modules/settings/routes/modules/automation_radarr.dart b/lib/modules/settings/routes/modules/automation_radarr.dart index 9ea8d31a..e7e55a4d 100644 --- a/lib/modules/settings/routes/modules/automation_radarr.dart +++ b/lib/modules/settings/routes/modules/automation_radarr.dart @@ -1,117 +1,2 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/radarr.dart'; - -class SettingsModulesRadarr extends StatefulWidget { - static const ROUTE_NAME = '/settings/modules/radarr'; - - @override - State createState() => _State(); -} - -class _State extends State { - final _scaffoldKey = GlobalKey(); - ProfileHiveObject _profile = Database.currentProfileObject; - - @override - Widget build(BuildContext context) => Scaffold( - key: _scaffoldKey, - body: _body, - appBar: _appBar, - ); - - Widget get _appBar => LSAppBar(title: 'Radarr'); - - Widget get _body => ValueListenableBuilder( - valueListenable: Database.profilesBox.listenable(), - builder: (context, box, widget) { - return LSListView( - children: [ - LSHeader( - text: 'Configuration', - subtitle: 'Mandatory configuration for Radarr functionality', - ), - LSCardTile( - title: LSTitle(text: 'Enable Radarr'), - subtitle: null, - trailing: Switch( - value: _profile.radarrEnabled ?? false, - onChanged: (value) { - _profile.radarrEnabled = value; - _profile.save(); - }, - ), - ), - LSCardTile( - title: LSTitle(text: 'Host'), - subtitle: LSSubtitle( - text: _profile.radarrHost == null || _profile.radarrHost == '' - ? 'Not Set' - : _profile.radarrHost - ), - trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: _changeHost, - ), - LSCardTile( - title: LSTitle(text: 'API Key'), - subtitle: LSSubtitle( - text: _profile.radarrKey == null || _profile.radarrKey == '' - ? 'Not Set' - : '••••••••••••' - ), - trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: _changeKey, - ), - LSDivider(), - LSButton( - text: 'Test Connection', - onTap: _testConnection, - ), - LSHeader( - text: 'Advanced', - subtitle: 'Advanced options for users with non-standard networking configurations. Be careful!', - ), - LSCardTile( - title: LSTitle(text: 'Strict SSL/TLS Validation'), - subtitle: LSSubtitle(text: 'For Invalid Certificates'), - trailing: Switch( - value: _profile.radarrStrictTLS ?? true, - onChanged: (value) async { - if(value) { - _profile.radarrStrictTLS = value; - _profile.save(); - } else { - List _values = await LSDialogSettings.toggleStrictTLS(context); - if(_values[0]) { - _profile.radarrStrictTLS = value; - _profile.save(); - } - } - }, - ), - ), - ], - ); - }, - ); - - Future _changeHost() async { - List _values = await LSDialogSystem.editText(context, 'Radarr Host', prefill: _profile.radarrHost ?? '', showHostHint: true); - if(_values[0]) { - _profile.radarrHost = _values[1]; - _profile.save(); - } - } - - Future _changeKey() async { - List _values = await LSDialogSystem.editText(context, 'Radarr API Key', prefill: _profile.radarrKey ?? ''); - if(_values[0]) { - _profile.radarrKey = _values[1]; - _profile.save(); - } - } - - Future _testConnection() async => await RadarrAPI.from(_profile).testConnection() - ? LSSnackBar(context: context, title: 'Connected Successfully', message: 'Radarr is ready to use with LunaSea', type: SNACKBAR_TYPE.success) - : LSSnackBar(context: context, title: 'Connection Test Failed', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure); -} +export './automation_radarr/automation_radarr.dart'; +export './automation_radarr/automation_radarr_headers.dart'; diff --git a/lib/modules/settings/routes/modules/automation_radarr/automation_radarr.dart b/lib/modules/settings/routes/modules/automation_radarr/automation_radarr.dart new file mode 100644 index 00000000..4b9ba607 --- /dev/null +++ b/lib/modules/settings/routes/modules/automation_radarr/automation_radarr.dart @@ -0,0 +1,132 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/radarr.dart'; +import 'package:lunasea/modules/settings.dart'; + +class SettingsModulesRadarr extends StatefulWidget { + static const ROUTE_NAME = '/settings/modules/radarr'; + + @override + State createState() => _State(); +} + +class _State extends State { + final _scaffoldKey = GlobalKey(); + ProfileHiveObject _profile = Database.currentProfileObject; + + @override + Widget build(BuildContext context) => Scaffold( + key: _scaffoldKey, + body: _body, + appBar: _appBar, + ); + + Widget get _appBar => LSAppBar(title: 'Radarr'); + + List get _configuration => [ + LSHeader( + text: 'Configuration', + subtitle: 'Mandatory configuration for Radarr functionality', + ), + LSCardTile( + title: LSTitle(text: 'Enable Radarr'), + subtitle: null, + trailing: Switch( + value: _profile.radarrEnabled ?? false, + onChanged: (value) { + _profile.radarrEnabled = value; + _profile.save(); + }, + ), + ), + LSCardTile( + title: LSTitle(text: 'Host'), + subtitle: LSSubtitle( + text: _profile.radarrHost == null || _profile.radarrHost == '' + ? 'Not Set' + : _profile.radarrHost + ), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: _changeHost, + ), + LSCardTile( + title: LSTitle(text: 'API Key'), + subtitle: LSSubtitle( + text: _profile.radarrKey == null || _profile.radarrKey == '' + ? 'Not Set' + : '••••••••••••' + ), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: _changeKey, + ), + LSDivider(), + LSButton( + text: 'Test Connection', + onTap: _testConnection, + ), + ]; + + List get _advanced => [ + LSHeader( + text: 'Advanced', + subtitle: 'Advanced options for users with non-standard networking configurations. Be careful!', + ), + LSCardTile( + title: LSTitle(text: 'Custom Headers'), + subtitle: LSSubtitle(text: 'Add Custom Headers to Requests'), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: () async => Navigator.of(context).pushNamed(SettingsModulesRadarrHeaders.ROUTE_NAME), + ), + LSCardTile( + title: LSTitle(text: 'Strict SSL/TLS Validation'), + subtitle: LSSubtitle(text: 'For Invalid Certificates'), + trailing: Switch( + value: _profile.radarrStrictTLS ?? true, + onChanged: (value) async { + if(value) { + _profile.radarrStrictTLS = value; + _profile.save(); + } else { + List _values = await SettingsDialogs.toggleStrictTLS(context); + if(_values[0]) { + _profile.radarrStrictTLS = value; + _profile.save(); + } + } + }, + ), + ), + ]; + + Widget get _body => ValueListenableBuilder( + valueListenable: Database.profilesBox.listenable(), + builder: (context, box, widget) { + return LSListView( + children: [ + ..._configuration, + ..._advanced, + ], + ); + }, + ); + + Future _changeHost() async { + List _values = await SettingsDialogs.editHost(context, 'Radarr Host', prefill: _profile.radarrHost ?? ''); + if(_values[0]) { + _profile.radarrHost = _values[1]; + _profile.save(); + } + } + + Future _changeKey() async { + List _values = await GlobalDialogs.editText(context, 'Radarr API Key', prefill: _profile.radarrKey ?? ''); + if(_values[0]) { + _profile.radarrKey = _values[1]; + _profile.save(); + } + } + + Future _testConnection() async => await RadarrAPI.from(_profile).testConnection() + ? LSSnackBar(context: context, title: 'Connected Successfully', message: 'Radarr is ready to use with LunaSea', type: SNACKBAR_TYPE.success) + : LSSnackBar(context: context, title: 'Connection Test Failed', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure); +} diff --git a/lib/modules/settings/routes/modules/automation_radarr/automation_radarr_headers.dart b/lib/modules/settings/routes/modules/automation_radarr/automation_radarr_headers.dart new file mode 100644 index 00000000..b989a3a6 --- /dev/null +++ b/lib/modules/settings/routes/modules/automation_radarr/automation_radarr_headers.dart @@ -0,0 +1,110 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/settings.dart'; + +class SettingsModulesRadarrHeaders extends StatefulWidget { + static const ROUTE_NAME = '/settings/modules/radarr/headers'; + + @override + State createState() => _State(); +} + +class _State extends State { + ProfileHiveObject _profile = Database.currentProfileObject; + + @override + Widget build(BuildContext context) => Scaffold( + appBar: _appBar, + body: _body, + ); + + Widget get _appBar => LSAppBar(title: 'Custom Headers'); + + Widget get _body => ValueListenableBuilder( + valueListenable: Database.profilesBox.listenable(), + builder: (context, profile, widget) => LSListView( + children: [ + ..._headers, + ], + ), + ); + + List get _headers => [ + LSHeader( + text: 'Custom Headers', + subtitle: 'Define custom headers that will be attached to every request made to the module.', + ), + if((_profile.getRadarr()['headers'] as Map).isEmpty) LSGenericMessage( + text: 'No Custom Headers Added', + ), + ..._list, + LSDivider(), + LSButton( + text: 'Add Header', + onTap: () async => _showAddPrompt(), + ), + ]; + + List get _list { + Map headers = Map.from(_profile.getRadarr()['headers']); + List list = []; + headers.forEach((key, value) => list.add(LSCardTile( + title: LSTitle(text: key.toString()), + subtitle: LSSubtitle(text: value.toString()), + trailing: LSIconButton( + icon: Icons.delete, + color: LSColors.red, + onPressed: () async => _deleteIndexer(key.toString(), value.toString()), + ), + ))); + list.sort((a,b) => (a.title as LSTitle).text.compareTo((b.title as LSTitle).text)); + return list; + } + + Future _showAddPrompt() async { + List results = await SettingsDialogs.addHeader(context); + if(results[0]) switch(results[1]) { + case 1: _showAuthenticationPrompt(); break; + case 100: _showCustomPrompt(); break; + default: break; + } + } + + Future _showAuthenticationPrompt() async { + List results = await SettingsDialogs.addAuthenticationHeader(context); + if(results[0]) { + Map _headers = Map.from(_profile.getRadarr()['headers']); + String _auth = base64.encode(utf8.encode('${results[1]}:${results[2]}')); + _headers.addAll({'Authorization': 'Basic $_auth'}); + _profile.radarrHeaders = _headers; + _profile.save(); + } + } + + Future _showCustomPrompt() async { + List results = await SettingsDialogs.addCustomHeader(context); + if(results[0]) { + Map _headers = Map.from(_profile.getRadarr()['headers']); + _headers.addAll({results[1]: results[2]}); + _profile.radarrHeaders = _headers; + _profile.save(); + } + } + + Future _deleteIndexer(String key, String value) async { + List results = await SettingsDialogs.deleteHeader(context); + if(results[0]) { + Map _headers = Map.from(_profile.getRadarr()['headers']); + _headers.remove(key); + _profile.radarrHeaders = _headers; + _profile.save(); + LSSnackBar( + context: context, + message: key, + title: 'Header Deleted', + type: SNACKBAR_TYPE.success, + ); + } + } +} diff --git a/lib/modules/settings/routes/modules/automation_sonarr.dart b/lib/modules/settings/routes/modules/automation_sonarr.dart index c0aa0e33..9846d25c 100644 --- a/lib/modules/settings/routes/modules/automation_sonarr.dart +++ b/lib/modules/settings/routes/modules/automation_sonarr.dart @@ -1,136 +1,2 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SettingsModulesSonarr extends StatefulWidget { - static const ROUTE_NAME = '/settings/modules/sonarr'; - - @override - State createState() => _State(); -} - -class _State extends State { - final _scaffoldKey = GlobalKey(); - ProfileHiveObject _profile = Database.currentProfileObject; - - @override - Widget build(BuildContext context) => Scaffold( - key: _scaffoldKey, - body: _body, - appBar: _appBar, - ); - - Widget get _appBar => LSAppBar(title: 'Sonarr'); - - Widget get _body => ValueListenableBuilder( - valueListenable: Database.profilesBox.listenable(), - builder: (context, box, widget) { - return LSListView( - children: [ - ..._configuration, - ..._advanced, - ], - ); - }, - ); - - List get _configuration => [ - LSHeader( - text: 'Configuration', - subtitle: 'Mandatory configuration for Sonarr functionality', - ), - LSCardTile( - title: LSTitle(text: 'Enable Sonarr'), - subtitle: null, - trailing: Switch( - value: _profile.sonarrEnabled ?? false, - onChanged: (value) { - _profile.sonarrEnabled = value; - _profile.save(); - }, - ), - ), - LSCardTile( - title: LSTitle(text: 'Host'), - subtitle: LSSubtitle( - text: _profile.sonarrHost == null || _profile.sonarrHost == '' - ? 'Not Set' - : _profile.sonarrHost - ), - trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: _changeHost, - ), - LSCardTile( - title: LSTitle(text: 'API Key'), - subtitle: LSSubtitle( - text: _profile.sonarrKey == null || _profile.sonarrKey == '' - ? 'Not Set' - : '••••••••••••' - ), - trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: _changeKey, - ), - // LSCardTile( - // title: LSTitle(text: 'Sonarr v3'), - // subtitle: LSSubtitle(text: 'Enable Sonarr v3 Features'), - // trailing: Switch( - // value: _profile.sonarrVersion3 ?? false, - // onChanged: (value) async { - // _profile.sonarrVersion3 = value; - // _profile.save(); - // }, - // ), - // ), - LSDivider(), - LSButton( - text: 'Test Connection', - onTap: _testConnection, - ), - ]; - - List get _advanced => [ - LSHeader( - text: 'Advanced', - subtitle: 'Advanced options for users with non-standard networking configurations. Be careful!', - ), - LSCardTile( - title: LSTitle(text: 'Strict SSL/TLS Validation'), - subtitle: LSSubtitle(text: 'For Invalid Certificates'), - trailing: Switch( - value: _profile.sonarrStrictTLS ?? true, - onChanged: (value) async { - if(value) { - _profile.sonarrStrictTLS = value; - _profile.save(); - } else { - List _values = await LSDialogSettings.toggleStrictTLS(context); - if(_values[0]) { - _profile.sonarrStrictTLS = value; - _profile.save(); - } - } - }, - ), - ), - ]; - - Future _changeHost() async { - List _values = await LSDialogSystem.editText(context, 'Sonarr Host', prefill: _profile.sonarrHost ?? '', showHostHint: true); - if(_values[0]) { - _profile.sonarrHost = _values[1]; - _profile.save(); - } - } - - Future _changeKey() async { - List _values = await LSDialogSystem.editText(context, 'Sonarr API Key', prefill: _profile.sonarrKey ?? ''); - if(_values[0]) { - _profile.sonarrKey = _values[1]; - _profile.save(); - } - } - - Future _testConnection() async => await SonarrAPI.from(_profile).testConnection() - ? LSSnackBar(context: context, title: 'Connected Successfully', message: 'Sonarr is ready to use with LunaSea', type: SNACKBAR_TYPE.success) - : LSSnackBar(context: context, title: 'Connection Test Failed', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure); -} +export './automation_sonarr/automation_sonarr.dart'; +export './automation_sonarr/automation_sonarr_headers.dart'; diff --git a/lib/modules/settings/routes/modules/automation_sonarr/automation_sonarr.dart b/lib/modules/settings/routes/modules/automation_sonarr/automation_sonarr.dart new file mode 100644 index 00000000..2beca061 --- /dev/null +++ b/lib/modules/settings/routes/modules/automation_sonarr/automation_sonarr.dart @@ -0,0 +1,143 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; +import 'package:lunasea/modules/settings.dart'; + +class SettingsModulesSonarr extends StatefulWidget { + static const ROUTE_NAME = '/settings/modules/sonarr'; + + @override + State createState() => _State(); +} + +class _State extends State { + final _scaffoldKey = GlobalKey(); + ProfileHiveObject _profile = Database.currentProfileObject; + + @override + Widget build(BuildContext context) => Scaffold( + key: _scaffoldKey, + body: _body, + appBar: _appBar, + ); + + Widget get _appBar => LSAppBar(title: 'Sonarr'); + + Widget get _body => ValueListenableBuilder( + valueListenable: Database.profilesBox.listenable(), + builder: (context, box, widget) { + return LSListView( + children: [ + ..._configuration, + ..._advanced, + ], + ); + }, + ); + + List get _configuration => [ + LSHeader( + text: 'Configuration', + subtitle: 'Mandatory configuration for Sonarr functionality', + ), + LSCardTile( + title: LSTitle(text: 'Enable Sonarr'), + subtitle: null, + trailing: Switch( + value: _profile.sonarrEnabled ?? false, + onChanged: (value) { + _profile.sonarrEnabled = value; + _profile.save(); + }, + ), + ), + LSCardTile( + title: LSTitle(text: 'Host'), + subtitle: LSSubtitle( + text: _profile.sonarrHost == null || _profile.sonarrHost == '' + ? 'Not Set' + : _profile.sonarrHost + ), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: _changeHost, + ), + LSCardTile( + title: LSTitle(text: 'API Key'), + subtitle: LSSubtitle( + text: _profile.sonarrKey == null || _profile.sonarrKey == '' + ? 'Not Set' + : '••••••••••••' + ), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: _changeKey, + ), + // LSCardTile( + // title: LSTitle(text: 'Sonarr v3'), + // subtitle: LSSubtitle(text: 'Enable Sonarr v3 Features'), + // trailing: Switch( + // value: _profile.sonarrVersion3 ?? false, + // onChanged: (value) async { + // _profile.sonarrVersion3 = value; + // _profile.save(); + // }, + // ), + // ), + LSDivider(), + LSButton( + text: 'Test Connection', + onTap: _testConnection, + ), + ]; + + List get _advanced => [ + LSHeader( + text: 'Advanced', + subtitle: 'Advanced options for users with non-standard networking configurations. Be careful!', + ), + LSCardTile( + title: LSTitle(text: 'Custom Headers'), + subtitle: LSSubtitle(text: 'Add Custom Headers to Requests'), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: () async => Navigator.of(context).pushNamed(SettingsModulesSonarrHeaders.ROUTE_NAME), + ), + LSCardTile( + title: LSTitle(text: 'Strict SSL/TLS Validation'), + subtitle: LSSubtitle(text: 'For Invalid Certificates'), + trailing: Switch( + value: _profile.sonarrStrictTLS ?? true, + onChanged: (value) async { + if(value) { + _profile.sonarrStrictTLS = value; + _profile.save(); + } else { + List _values = await SettingsDialogs.toggleStrictTLS(context); + if(_values[0]) { + _profile.sonarrStrictTLS = value; + _profile.save(); + } + } + }, + ), + ), + ]; + + Future _changeHost() async { + List _values = await SettingsDialogs.editHost(context, 'Sonarr Host', prefill: _profile.sonarrHost ?? ''); + if(_values[0]) { + _profile.sonarrHost = _values[1]; + _profile.save(); + } + } + + Future _changeKey() async { + List _values = await GlobalDialogs.editText(context, 'Sonarr API Key', prefill: _profile.sonarrKey ?? ''); + if(_values[0]) { + _profile.sonarrKey = _values[1]; + _profile.save(); + } + } + + Future _testConnection() async => await SonarrAPI.from(_profile).testConnection() + ? LSSnackBar(context: context, title: 'Connected Successfully', message: 'Sonarr is ready to use with LunaSea', type: SNACKBAR_TYPE.success) + : LSSnackBar(context: context, title: 'Connection Test Failed', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure); +} diff --git a/lib/modules/settings/routes/modules/automation_sonarr/automation_sonarr_headers.dart b/lib/modules/settings/routes/modules/automation_sonarr/automation_sonarr_headers.dart new file mode 100644 index 00000000..51cc59f7 --- /dev/null +++ b/lib/modules/settings/routes/modules/automation_sonarr/automation_sonarr_headers.dart @@ -0,0 +1,110 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/settings.dart'; + +class SettingsModulesSonarrHeaders extends StatefulWidget { + static const ROUTE_NAME = '/settings/modules/sonarr/headers'; + + @override + State createState() => _State(); +} + +class _State extends State { + ProfileHiveObject _profile = Database.currentProfileObject; + + @override + Widget build(BuildContext context) => Scaffold( + appBar: _appBar, + body: _body, + ); + + Widget get _appBar => LSAppBar(title: 'Custom Headers'); + + Widget get _body => ValueListenableBuilder( + valueListenable: Database.profilesBox.listenable(), + builder: (context, profile, widget) => LSListView( + children: [ + ..._headers, + ], + ), + ); + + List get _headers => [ + LSHeader( + text: 'Custom Headers', + subtitle: 'Define custom headers that will be attached to every request made to the module.', + ), + if((_profile.getSonarr()['headers'] as Map).isEmpty) LSGenericMessage( + text: 'No Custom Headers Added', + ), + ..._list, + LSDivider(), + LSButton( + text: 'Add Header', + onTap: () async => _showAddPrompt(), + ), + ]; + + List get _list { + Map headers = Map.from(_profile.getSonarr()['headers']); + List list = []; + headers.forEach((key, value) => list.add(LSCardTile( + title: LSTitle(text: key.toString()), + subtitle: LSSubtitle(text: value.toString()), + trailing: LSIconButton( + icon: Icons.delete, + color: LSColors.red, + onPressed: () async => _deleteIndexer(key.toString(), value.toString()), + ), + ))); + list.sort((a,b) => (a.title as LSTitle).text.compareTo((b.title as LSTitle).text)); + return list; + } + + Future _showAddPrompt() async { + List results = await SettingsDialogs.addHeader(context); + if(results[0]) switch(results[1]) { + case 1: _showAuthenticationPrompt(); break; + case 100: _showCustomPrompt(); break; + default: break; + } + } + + Future _showAuthenticationPrompt() async { + List results = await SettingsDialogs.addAuthenticationHeader(context); + if(results[0]) { + Map _headers = Map.from(_profile.getSonarr()['headers']); + String _auth = base64.encode(utf8.encode('${results[1]}:${results[2]}')); + _headers.addAll({'Authorization': 'Basic $_auth'}); + _profile.sonarrHeaders = _headers; + _profile.save(); + } + } + + Future _showCustomPrompt() async { + List results = await SettingsDialogs.addCustomHeader(context); + if(results[0]) { + Map _headers = Map.from(_profile.getSonarr()['headers']); + _headers.addAll({results[1]: results[2]}); + _profile.sonarrHeaders = _headers; + _profile.save(); + } + } + + Future _deleteIndexer(String key, String value) async { + List results = await SettingsDialogs.deleteHeader(context); + if(results[0]) { + Map _headers = Map.from(_profile.getSonarr()['headers']); + _headers.remove(key); + _profile.sonarrHeaders = _headers; + _profile.save(); + LSSnackBar( + context: context, + message: key, + title: 'Header Deleted', + type: SNACKBAR_TYPE.success, + ); + } + } +} diff --git a/lib/modules/settings/routes/modules/clients_nzbget.dart b/lib/modules/settings/routes/modules/clients_nzbget.dart index 6ea60b73..1990d8df 100644 --- a/lib/modules/settings/routes/modules/clients_nzbget.dart +++ b/lib/modules/settings/routes/modules/clients_nzbget.dart @@ -1,155 +1,2 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/nzbget.dart'; - -class SettingsModulesNZBGet extends StatefulWidget { - static const ROUTE_NAME = '/settings/modules/nzbget'; - - @override - State createState() => _State(); -} - -class _State extends State { - final _scaffoldKey = GlobalKey(); - ProfileHiveObject _profile = Database.currentProfileObject; - - @override - Widget build(BuildContext context) => Scaffold( - key: _scaffoldKey, - body: _body, - appBar: _appBar, - ); - - Widget get _appBar => LSAppBar(title: 'NZBGet'); - - Widget get _body => ValueListenableBuilder( - valueListenable: Database.profilesBox.listenable(), - builder: (context, box, widget) { - return LSListView( - children: [ - LSHeader( - text: 'Configuration', - subtitle: 'Mandatory configuration for NZBGet functionality', - ), - LSCardTile( - title: LSTitle(text: 'Enable NZBGet'), - subtitle: null, - trailing: Switch( - value: _profile.nzbgetEnabled ?? false, - onChanged: (value) { - _profile.nzbgetEnabled = value; - _profile.save(); - }, - ), - ), - LSCardTile( - title: LSTitle(text: 'Host'), - subtitle: LSSubtitle( - text: _profile.nzbgetHost == null || _profile.nzbgetHost == '' - ? 'Not Set' - : _profile.nzbgetHost - ), - trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: _changeHost, - ), - LSCardTile( - title: LSTitle(text: 'Username'), - subtitle: LSSubtitle( - text: _profile.nzbgetUser == null || _profile.nzbgetUser == '' - ? 'Not Set' - : _profile.nzbgetUser - ), - trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: _changeUser, - ), - LSCardTile( - title: LSTitle(text: 'Password'), - subtitle: LSSubtitle( - text: _profile.nzbgetPass == null || _profile.nzbgetPass == '' - ? 'Not Set' - : '••••••••••••' - ), - trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: _changePass, - ), - LSDivider(), - LSButton( - text: 'Test Connection', - onTap: _testConnection, - ), - LSHeader( - text: 'Advanced', - subtitle: 'Advanced options for users with non-standard networking configurations. Be careful!', - ), - LSCardTile( - title: LSTitle(text: 'Strict SSL/TLS Validation'), - subtitle: LSSubtitle(text: 'For Invalid Certificates'), - trailing: Switch( - value: _profile.nzbgetStrictTLS ?? true, - onChanged: (value) async { - if(value) { - _profile.nzbgetStrictTLS = value; - _profile.save(); - } else { - List _values = await LSDialogSettings.toggleStrictTLS(context); - if(_values[0]) { - _profile.nzbgetStrictTLS = value; - _profile.save(); - } - } - }, - ), - ), - LSCardTile( - title: LSTitle(text: 'Use Basic Authentication'), - subtitle: LSSubtitle(text: 'Different Authentication Method'), - trailing: Switch( - value: _profile.nzbgetBasicAuth ?? false, - onChanged: (value) async { - if(!value) { - _profile.nzbgetBasicAuth = value; - _profile.save(); - } else { - List _values = await LSDialogSettings.nzbgetBasicAuthentication(context); - if(_values[0]) { - _profile.nzbgetBasicAuth = value; - _profile.save(); - } - } - }, - ), - ), - ], - padBottom: true, - ); - }, - ); - - Future _changeHost() async { - List _values = await LSDialogSystem.editText(context, 'NZBGet Host', prefill: _profile.nzbgetHost ?? '', showHostHint: true); - if(_values[0]) { - _profile.nzbgetHost = _values[1]; - _profile.save(); - } - } - - Future _changeUser() async { - List _values = await LSDialogSystem.editText(context, 'NZBGet Username', prefill: _profile.nzbgetUser ?? ''); - if(_values[0]) { - _profile.nzbgetUser = _values[1]; - _profile.save(); - } - } - - Future _changePass() async { - List _values = await LSDialogSystem.editText(context, 'NZBGet Password', prefill: _profile.nzbgetPass ?? ''); - if(_values[0]) { - _profile.nzbgetPass = _values[1]; - _profile.save(); - } - } - - Future _testConnection() async => await NZBGetAPI.from(_profile).testConnection() - ? LSSnackBar(context: context, title: 'Connected Successfully', message: 'NZBGet is ready to use with LunaSea', type: SNACKBAR_TYPE.success) - : LSSnackBar(context: context, title: 'Connection Test Failed', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure); -} +export './clients_nzbget/clients_nzbget.dart'; +export './clients_nzbget/clients_nzbget_headers.dart'; diff --git a/lib/modules/settings/routes/modules/clients_nzbget/clients_nzbget.dart b/lib/modules/settings/routes/modules/clients_nzbget/clients_nzbget.dart new file mode 100644 index 00000000..994e89a1 --- /dev/null +++ b/lib/modules/settings/routes/modules/clients_nzbget/clients_nzbget.dart @@ -0,0 +1,170 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/nzbget.dart'; +import 'package:lunasea/modules/settings.dart'; + +class SettingsModulesNZBGet extends StatefulWidget { + static const ROUTE_NAME = '/settings/modules/nzbget'; + + @override + State createState() => _State(); +} + +class _State extends State { + final _scaffoldKey = GlobalKey(); + ProfileHiveObject _profile = Database.currentProfileObject; + + @override + Widget build(BuildContext context) => Scaffold( + key: _scaffoldKey, + body: _body, + appBar: _appBar, + ); + + Widget get _appBar => LSAppBar(title: 'NZBGet'); + + List get _configuration => [ + LSHeader( + text: 'Configuration', + subtitle: 'Mandatory configuration for NZBGet functionality', + ), + LSCardTile( + title: LSTitle(text: 'Enable NZBGet'), + subtitle: null, + trailing: Switch( + value: _profile.nzbgetEnabled ?? false, + onChanged: (value) { + _profile.nzbgetEnabled = value; + _profile.save(); + }, + ), + ), + LSCardTile( + title: LSTitle(text: 'Host'), + subtitle: LSSubtitle( + text: _profile.nzbgetHost == null || _profile.nzbgetHost == '' + ? 'Not Set' + : _profile.nzbgetHost + ), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: _changeHost, + ), + LSCardTile( + title: LSTitle(text: 'Username'), + subtitle: LSSubtitle( + text: _profile.nzbgetUser == null || _profile.nzbgetUser == '' + ? 'Not Set' + : _profile.nzbgetUser + ), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: _changeUser, + ), + LSCardTile( + title: LSTitle(text: 'Password'), + subtitle: LSSubtitle( + text: _profile.nzbgetPass == null || _profile.nzbgetPass == '' + ? 'Not Set' + : '••••••••••••' + ), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: _changePass, + ), + LSDivider(), + LSButton( + text: 'Test Connection', + onTap: _testConnection, + ), + ]; + + List get _advanced => [ + LSHeader( + text: 'Advanced', + subtitle: 'Advanced options for users with non-standard networking configurations. Be careful!', + ), + LSCardTile( + title: LSTitle(text: 'Custom Headers'), + subtitle: LSSubtitle(text: 'Add Custom Headers to Requests'), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: () async => Navigator.of(context).pushNamed(SettingsModulesNZBGetHeaders.ROUTE_NAME), + ), + LSCardTile( + title: LSTitle(text: 'Strict SSL/TLS Validation'), + subtitle: LSSubtitle(text: 'For Invalid Certificates'), + trailing: Switch( + value: _profile.nzbgetStrictTLS ?? true, + onChanged: (value) async { + if(value) { + _profile.nzbgetStrictTLS = value; + _profile.save(); + } else { + List _values = await SettingsDialogs.toggleStrictTLS(context); + if(_values[0]) { + _profile.nzbgetStrictTLS = value; + _profile.save(); + } + } + }, + ), + ), + LSCardTile( + title: LSTitle(text: 'Use Basic Authentication'), + subtitle: LSSubtitle(text: 'Different Authentication Method'), + trailing: Switch( + value: _profile.nzbgetBasicAuth ?? false, + onChanged: (value) async { + if(!value) { + _profile.nzbgetBasicAuth = value; + _profile.save(); + } else { + List _values = await SettingsDialogs.nzbgetBasicAuthentication(context); + if(_values[0]) { + _profile.nzbgetBasicAuth = value; + _profile.save(); + } + } + }, + ), + ), + ]; + + Widget get _body => ValueListenableBuilder( + valueListenable: Database.profilesBox.listenable(), + builder: (context, box, widget) { + return LSListView( + children: [ + ..._configuration, + ..._advanced, + ], + padBottom: true, + ); + }, + ); + + Future _changeHost() async { + List _values = await SettingsDialogs.editHost(context, 'NZBGet Host', prefill: _profile.nzbgetHost ?? ''); + if(_values[0]) { + _profile.nzbgetHost = _values[1]; + _profile.save(); + } + } + + Future _changeUser() async { + List _values = await GlobalDialogs.editText(context, 'NZBGet Username', prefill: _profile.nzbgetUser ?? ''); + if(_values[0]) { + _profile.nzbgetUser = _values[1]; + _profile.save(); + } + } + + Future _changePass() async { + List _values = await GlobalDialogs.editText(context, 'NZBGet Password', prefill: _profile.nzbgetPass ?? ''); + if(_values[0]) { + _profile.nzbgetPass = _values[1]; + _profile.save(); + } + } + + Future _testConnection() async => await NZBGetAPI.from(_profile).testConnection() + ? LSSnackBar(context: context, title: 'Connected Successfully', message: 'NZBGet is ready to use with LunaSea', type: SNACKBAR_TYPE.success) + : LSSnackBar(context: context, title: 'Connection Test Failed', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure); +} diff --git a/lib/modules/settings/routes/modules/clients_nzbget/clients_nzbget_headers.dart b/lib/modules/settings/routes/modules/clients_nzbget/clients_nzbget_headers.dart new file mode 100644 index 00000000..0b72084c --- /dev/null +++ b/lib/modules/settings/routes/modules/clients_nzbget/clients_nzbget_headers.dart @@ -0,0 +1,110 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/settings.dart'; + +class SettingsModulesNZBGetHeaders extends StatefulWidget { + static const ROUTE_NAME = '/settings/modules/nzbget/headers'; + + @override + State createState() => _State(); +} + +class _State extends State { + ProfileHiveObject _profile = Database.currentProfileObject; + + @override + Widget build(BuildContext context) => Scaffold( + appBar: _appBar, + body: _body, + ); + + Widget get _appBar => LSAppBar(title: 'Custom Headers'); + + Widget get _body => ValueListenableBuilder( + valueListenable: Database.profilesBox.listenable(), + builder: (context, profile, widget) => LSListView( + children: [ + ..._headers, + ], + ), + ); + + List get _headers => [ + LSHeader( + text: 'Custom Headers', + subtitle: 'Define custom headers that will be attached to every request made to the module.', + ), + if((_profile.getNZBGet()['headers'] as Map).isEmpty) LSGenericMessage( + text: 'No Custom Headers Added', + ), + ..._list, + LSDivider(), + LSButton( + text: 'Add Header', + onTap: () async => _showAddPrompt(), + ), + ]; + + List get _list { + Map headers = Map.from(_profile.getNZBGet()['headers']); + List list = []; + headers.forEach((key, value) => list.add(LSCardTile( + title: LSTitle(text: key.toString()), + subtitle: LSSubtitle(text: value.toString()), + trailing: LSIconButton( + icon: Icons.delete, + color: LSColors.red, + onPressed: () async => _deleteIndexer(key.toString(), value.toString()), + ), + ))); + list.sort((a,b) => (a.title as LSTitle).text.compareTo((b.title as LSTitle).text)); + return list; + } + + Future _showAddPrompt() async { + List results = await SettingsDialogs.addHeader(context); + if(results[0]) switch(results[1]) { + case 1: _showAuthenticationPrompt(); break; + case 100: _showCustomPrompt(); break; + default: break; + } + } + + Future _showAuthenticationPrompt() async { + List results = await SettingsDialogs.addAuthenticationHeader(context); + if(results[0]) { + Map _headers = Map.from(_profile.getNZBGet()['headers']); + String _auth = base64.encode(utf8.encode('${results[1]}:${results[2]}')); + _headers.addAll({'Authorization': 'Basic $_auth'}); + _profile.nzbgetHeaders = _headers; + _profile.save(); + } + } + + Future _showCustomPrompt() async { + List results = await SettingsDialogs.addCustomHeader(context); + if(results[0]) { + Map _headers = Map.from(_profile.getNZBGet()['headers']); + _headers.addAll({results[1]: results[2]}); + _profile.nzbgetHeaders = _headers; + _profile.save(); + } + } + + Future _deleteIndexer(String key, String value) async { + List results = await SettingsDialogs.deleteHeader(context); + if(results[0]) { + Map _headers = Map.from(_profile.getNZBGet()['headers']); + _headers.remove(key); + _profile.nzbgetHeaders = _headers; + _profile.save(); + LSSnackBar( + context: context, + message: key, + title: 'Header Deleted', + type: SNACKBAR_TYPE.success, + ); + } + } +} diff --git a/lib/modules/settings/routes/modules/clients_sabnzbd.dart b/lib/modules/settings/routes/modules/clients_sabnzbd.dart index 5c821f2c..f5add9aa 100644 --- a/lib/modules/settings/routes/modules/clients_sabnzbd.dart +++ b/lib/modules/settings/routes/modules/clients_sabnzbd.dart @@ -1,117 +1,2 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sabnzbd.dart'; - -class SettingsModulesSABnzbd extends StatefulWidget { - static const ROUTE_NAME = '/settings/modules/sabnzbd'; - - @override - State createState() => _State(); -} - -class _State extends State { - final _scaffoldKey = GlobalKey(); - ProfileHiveObject _profile = Database.currentProfileObject; - - @override - Widget build(BuildContext context) => Scaffold( - key: _scaffoldKey, - body: _body, - appBar: _appBar, - ); - - Widget get _appBar => LSAppBar(title: 'SABnzbd'); - - Widget get _body => ValueListenableBuilder( - valueListenable: Database.profilesBox.listenable(), - builder: (context, box, widget) { - return LSListView( - children: [ - LSHeader( - text: 'Configuration', - subtitle: 'Mandatory configuration for SABnzbd functionality', - ), - LSCardTile( - title: LSTitle(text: 'Enable SABnzbd'), - subtitle: null, - trailing: Switch( - value: _profile.sabnzbdEnabled ?? false, - onChanged: (value) { - _profile.sabnzbdEnabled = value; - _profile.save(); - }, - ), - ), - LSCardTile( - title: LSTitle(text: 'Host'), - subtitle: LSSubtitle( - text: _profile.sabnzbdHost == null || _profile.sabnzbdHost == '' - ? 'Not Set' - : _profile.sabnzbdHost - ), - trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: _changeHost, - ), - LSCardTile( - title: LSTitle(text: 'API Key'), - subtitle: LSSubtitle( - text: _profile.sabnzbdKey == null || _profile.sabnzbdKey == '' - ? 'Not Set' - : '••••••••••••' - ), - trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: _changeKey, - ), - LSDivider(), - LSButton( - text: 'Test Connection', - onTap: _testConnection, - ), - LSHeader( - text: 'Advanced', - subtitle: 'Advanced options for users with non-standard networking configurations. Be careful!', - ), - LSCardTile( - title: LSTitle(text: 'Strict SSL/TLS Validation'), - subtitle: LSSubtitle(text: 'For Invalid Certificates'), - trailing: Switch( - value: _profile.sabnzbdStrictTLS ?? true, - onChanged: (value) async { - if(value) { - _profile.sabnzbdStrictTLS = value; - _profile.save(); - } else { - List _values = await LSDialogSettings.toggleStrictTLS(context); - if(_values[0]) { - _profile.sabnzbdStrictTLS = value; - _profile.save(); - } - } - }, - ), - ), - ], - ); - }, - ); - - Future _changeHost() async { - List _values = await LSDialogSystem.editText(context, 'SABnzbd Host', prefill: _profile.sabnzbdHost ?? '', showHostHint: true); - if(_values[0]) { - _profile.sabnzbdHost = _values[1]; - _profile.save(); - } - } - - Future _changeKey() async { - List _values = await LSDialogSystem.editText(context, 'SABnzbd API Key', prefill: _profile.sabnzbdKey ?? ''); - if(_values[0]) { - _profile.sabnzbdKey = _values[1]; - _profile.save(); - } - } - - Future _testConnection() async => await SABnzbdAPI.from(_profile).testConnection() - ? LSSnackBar(context: context, title: 'Connected Successfully', message: 'SABnzbd is ready to use with LunaSea', type: SNACKBAR_TYPE.success) - : LSSnackBar(context: context, title: 'Connection Test Failed', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure); -} +export './clients_sabnzbd/clients_sabnzbd.dart'; +export './clients_sabnzbd/clients_sabnzbd_headers.dart'; diff --git a/lib/modules/settings/routes/modules/clients_sabnzbd/clients_sabnzbd.dart b/lib/modules/settings/routes/modules/clients_sabnzbd/clients_sabnzbd.dart new file mode 100644 index 00000000..5497a184 --- /dev/null +++ b/lib/modules/settings/routes/modules/clients_sabnzbd/clients_sabnzbd.dart @@ -0,0 +1,132 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sabnzbd.dart'; +import 'package:lunasea/modules/settings.dart'; + +class SettingsModulesSABnzbd extends StatefulWidget { + static const ROUTE_NAME = '/settings/modules/sabnzbd'; + + @override + State createState() => _State(); +} + +class _State extends State { + final _scaffoldKey = GlobalKey(); + ProfileHiveObject _profile = Database.currentProfileObject; + + @override + Widget build(BuildContext context) => Scaffold( + key: _scaffoldKey, + body: _body, + appBar: _appBar, + ); + + Widget get _appBar => LSAppBar(title: 'SABnzbd'); + + List get _configuration => [ + LSHeader( + text: 'Configuration', + subtitle: 'Mandatory configuration for SABnzbd functionality', + ), + LSCardTile( + title: LSTitle(text: 'Enable SABnzbd'), + subtitle: null, + trailing: Switch( + value: _profile.sabnzbdEnabled ?? false, + onChanged: (value) { + _profile.sabnzbdEnabled = value; + _profile.save(); + }, + ), + ), + LSCardTile( + title: LSTitle(text: 'Host'), + subtitle: LSSubtitle( + text: _profile.sabnzbdHost == null || _profile.sabnzbdHost == '' + ? 'Not Set' + : _profile.sabnzbdHost + ), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: _changeHost, + ), + LSCardTile( + title: LSTitle(text: 'API Key'), + subtitle: LSSubtitle( + text: _profile.sabnzbdKey == null || _profile.sabnzbdKey == '' + ? 'Not Set' + : '••••••••••••' + ), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: _changeKey, + ), + LSDivider(), + LSButton( + text: 'Test Connection', + onTap: _testConnection, + ), + ]; + + List get _advanced => [ + LSHeader( + text: 'Advanced', + subtitle: 'Advanced options for users with non-standard networking configurations. Be careful!', + ), + LSCardTile( + title: LSTitle(text: 'Custom Headers'), + subtitle: LSSubtitle(text: 'Add Custom Headers to Requests'), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: () async => Navigator.of(context).pushNamed(SettingsModulesSABnzbdHeaders.ROUTE_NAME), + ), + LSCardTile( + title: LSTitle(text: 'Strict SSL/TLS Validation'), + subtitle: LSSubtitle(text: 'For Invalid Certificates'), + trailing: Switch( + value: _profile.sabnzbdStrictTLS ?? true, + onChanged: (value) async { + if(value) { + _profile.sabnzbdStrictTLS = value; + _profile.save(); + } else { + List _values = await SettingsDialogs.toggleStrictTLS(context); + if(_values[0]) { + _profile.sabnzbdStrictTLS = value; + _profile.save(); + } + } + }, + ), + ), + ]; + + Widget get _body => ValueListenableBuilder( + valueListenable: Database.profilesBox.listenable(), + builder: (context, box, widget) { + return LSListView( + children: [ + ..._configuration, + ..._advanced, + ], + ); + }, + ); + + Future _changeHost() async { + List _values = await SettingsDialogs.editHost(context, 'SABnzbd Host', prefill: _profile.sabnzbdHost ?? ''); + if(_values[0]) { + _profile.sabnzbdHost = _values[1]; + _profile.save(); + } + } + + Future _changeKey() async { + List _values = await GlobalDialogs.editText(context, 'SABnzbd API Key', prefill: _profile.sabnzbdKey ?? ''); + if(_values[0]) { + _profile.sabnzbdKey = _values[1]; + _profile.save(); + } + } + + Future _testConnection() async => await SABnzbdAPI.from(_profile).testConnection() + ? LSSnackBar(context: context, title: 'Connected Successfully', message: 'SABnzbd is ready to use with LunaSea', type: SNACKBAR_TYPE.success) + : LSSnackBar(context: context, title: 'Connection Test Failed', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure); +} diff --git a/lib/modules/settings/routes/modules/clients_sabnzbd/clients_sabnzbd_headers.dart b/lib/modules/settings/routes/modules/clients_sabnzbd/clients_sabnzbd_headers.dart new file mode 100644 index 00000000..4370984c --- /dev/null +++ b/lib/modules/settings/routes/modules/clients_sabnzbd/clients_sabnzbd_headers.dart @@ -0,0 +1,110 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/settings.dart'; + +class SettingsModulesSABnzbdHeaders extends StatefulWidget { + static const ROUTE_NAME = '/settings/modules/sabnzbd/headers'; + + @override + State createState() => _State(); +} + +class _State extends State { + ProfileHiveObject _profile = Database.currentProfileObject; + + @override + Widget build(BuildContext context) => Scaffold( + appBar: _appBar, + body: _body, + ); + + Widget get _appBar => LSAppBar(title: 'Custom Headers'); + + Widget get _body => ValueListenableBuilder( + valueListenable: Database.profilesBox.listenable(), + builder: (context, profile, widget) => LSListView( + children: [ + ..._headers, + ], + ), + ); + + List get _headers => [ + LSHeader( + text: 'Custom Headers', + subtitle: 'Define custom headers that will be attached to every request made to the module.', + ), + if((_profile.getSABnzbd()['headers'] as Map).isEmpty) LSGenericMessage( + text: 'No Custom Headers Added', + ), + ..._list, + LSDivider(), + LSButton( + text: 'Add Header', + onTap: () async => _showAddPrompt(), + ), + ]; + + List get _list { + Map headers = Map.from(_profile.getSABnzbd()['headers']); + List list = []; + headers.forEach((key, value) => list.add(LSCardTile( + title: LSTitle(text: key.toString()), + subtitle: LSSubtitle(text: value.toString()), + trailing: LSIconButton( + icon: Icons.delete, + color: LSColors.red, + onPressed: () async => _deleteIndexer(key.toString(), value.toString()), + ), + ))); + list.sort((a,b) => (a.title as LSTitle).text.compareTo((b.title as LSTitle).text)); + return list; + } + + Future _showAddPrompt() async { + List results = await SettingsDialogs.addHeader(context); + if(results[0]) switch(results[1]) { + case 1: _showAuthenticationPrompt(); break; + case 100: _showCustomPrompt(); break; + default: break; + } + } + + Future _showAuthenticationPrompt() async { + List results = await SettingsDialogs.addAuthenticationHeader(context); + if(results[0]) { + Map _headers = Map.from(_profile.getSABnzbd()['headers']); + String _auth = base64.encode(utf8.encode('${results[1]}:${results[2]}')); + _headers.addAll({'Authorization': 'Basic $_auth'}); + _profile.sabnzbdHeaders = _headers; + _profile.save(); + } + } + + Future _showCustomPrompt() async { + List results = await SettingsDialogs.addCustomHeader(context); + if(results[0]) { + Map _headers = Map.from(_profile.getSABnzbd()['headers']); + _headers.addAll({results[1]: results[2]}); + _profile.sabnzbdHeaders = _headers; + _profile.save(); + } + } + + Future _deleteIndexer(String key, String value) async { + List results = await SettingsDialogs.deleteHeader(context); + if(results[0]) { + Map _headers = Map.from(_profile.getSABnzbd()['headers']); + _headers.remove(key); + _profile.sabnzbdHeaders = _headers; + _profile.save(); + LSSnackBar( + context: context, + message: key, + title: 'Header Deleted', + type: SNACKBAR_TYPE.success, + ); + } + } +} diff --git a/lib/modules/settings/routes/modules/general_search_add.dart b/lib/modules/settings/routes/modules/general_search_add.dart index bdaa54c7..88411ab5 100644 --- a/lib/modules/settings/routes/modules/general_search_add.dart +++ b/lib/modules/settings/routes/modules/general_search_add.dart @@ -29,7 +29,7 @@ class _State extends State { subtitle: LSSubtitle(text: indexer.displayName == '' ? 'Not Set' : indexer.displayName), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async { - List _values = await LSDialogSystem.editText(context, 'Display Name', prefill: indexer.displayName); + List _values = await GlobalDialogs.editText(context, 'Display Name', prefill: indexer.displayName); setState(() => indexer.displayName = _values[0] ? _values[1] : indexer.displayName @@ -41,7 +41,7 @@ class _State extends State { subtitle: LSSubtitle(text: indexer.host == '' ? 'Not Set' : indexer.host), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async { - List _values = await LSDialogSystem.editText(context, 'Indexer API URL', prefill: indexer.host); + List _values = await GlobalDialogs.editText(context, 'Indexer API URL', prefill: indexer.host); setState(() => indexer.host = _values[0] ? _values[1] : indexer.host @@ -53,7 +53,7 @@ class _State extends State { subtitle: LSSubtitle(text: indexer.key == '' ? 'Not Set' : indexer.key), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async { - List _values = await LSDialogSystem.editText(context, 'Indexer API Key', prefill: indexer.key); + List _values = await GlobalDialogs.editText(context, 'Indexer API Key', prefill: indexer.key); setState(() => indexer.key = _values[0] ? _values[1] : indexer.key diff --git a/lib/modules/settings/routes/modules/general_search_edit.dart b/lib/modules/settings/routes/modules/general_search_edit.dart index 44cd58db..d028c45c 100644 --- a/lib/modules/settings/routes/modules/general_search_edit.dart +++ b/lib/modules/settings/routes/modules/general_search_edit.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; -import 'package:lunasea/core/database.dart'; import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/settings.dart'; class SettingsModulesSearchEditArguments { final IndexerHiveObject indexer; @@ -47,7 +47,7 @@ class _State extends State { subtitle: LSSubtitle(text: _arguments?.indexer?.displayName == '' ? 'Not Set' : _arguments?.indexer?.displayName ?? ''), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async { - List _values = await LSDialogSystem.editText(context, 'Display Name', prefill: _arguments?.indexer?.displayName); + List _values = await GlobalDialogs.editText(context, 'Display Name', prefill: _arguments?.indexer?.displayName); setState(() => _arguments?.indexer?.displayName = _values[0] ? _values[1] : _arguments?.indexer?.displayName @@ -60,7 +60,7 @@ class _State extends State { subtitle: LSSubtitle(text: _arguments?.indexer?.host == '' ? 'Not Set' : _arguments?.indexer?.host ?? ''), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async { - List _values = await LSDialogSystem.editText(context, 'URL', prefill: _arguments?.indexer?.host); + List _values = await GlobalDialogs.editText(context, 'URL', prefill: _arguments?.indexer?.host); setState(() => _arguments?.indexer?.host = _values[0] ? _values[1] : _arguments?.indexer?.host @@ -73,7 +73,7 @@ class _State extends State { subtitle: LSSubtitle(text: _arguments?.indexer?.key == '' ? 'Not Set' : _arguments?.indexer?.key ?? ''), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async { - List _values = await LSDialogSystem.editText(context, 'API Key', prefill: _arguments?.indexer?.key); + List _values = await GlobalDialogs.editText(context, 'API Key', prefill: _arguments?.indexer?.key); setState(() => _arguments?.indexer?.key = _values[0] ? _values[1] : _arguments?.indexer?.key @@ -91,7 +91,7 @@ class _State extends State { ); Future _deleteIndexer() async { - List _values = await LSDialogSettings.deleteIndexer(context); + List _values = await SettingsDialogs.deleteIndexer(context); if(_values[0]) { _arguments?.indexer?.delete(); Navigator.of(context).pop(['indexer_deleted', _arguments?.indexer?.displayName]); diff --git a/lib/modules/settings/routes/modules/general_wake_on_lan.dart b/lib/modules/settings/routes/modules/general_wake_on_lan.dart new file mode 100644 index 00000000..acdca4bf --- /dev/null +++ b/lib/modules/settings/routes/modules/general_wake_on_lan.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/settings.dart'; + +class SettingsModulesWakeOnLAN extends StatefulWidget { + static const ROUTE_NAME = '/settings/modules/wakeonlan'; + + @override + State createState() => _State(); +} + +class _State extends State { + final _scaffoldKey = GlobalKey(); + ProfileHiveObject _profile = Database.currentProfileObject; + + @override + Widget build(BuildContext context) => Scaffold( + key: _scaffoldKey, + body: _body, + appBar: _appBar, + ); + + Widget get _appBar => LSAppBar(title: 'Wake on LAN'); + + Widget get _body => ValueListenableBuilder( + valueListenable: Database.profilesBox.listenable(), + builder: (context, box, widget) { + return LSListView( + children: [ + ..._configuration, + ], + ); + }, + ); + + List get _configuration => [ + LSHeader( + text: 'Configuration', + subtitle: 'Mandatory configuration for Wake on LAN functionality', + ), + LSCardTile( + title: LSTitle(text: 'Enable Wake on LAN'), + subtitle: null, + trailing: Switch( + value: _profile.wakeOnLANEnabled ?? false, + onChanged: (value) { + _profile.wakeOnLANEnabled = value; + _profile.save(); + }, + ), + ), + LSCardTile( + title: LSTitle(text: 'Broadcast Address'), + subtitle: LSSubtitle( + text: _profile.wakeOnLANBroadcastAddress == null || _profile.wakeOnLANBroadcastAddress == '' + ? 'Not Set' + : _profile.wakeOnLANBroadcastAddress + ), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: _changeBroadcastAddress, + ), + LSCardTile( + title: LSTitle(text: 'Device MAC Address'), + subtitle: LSSubtitle( + text: _profile.wakeOnLANMACAddress == null || _profile.wakeOnLANMACAddress == '' + ? 'Not Set' + : _profile.wakeOnLANMACAddress + ), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: _changeMACAddress, + ), + ]; + + Future _changeBroadcastAddress() async { + List _values = await SettingsDialogs.editBroadcastAddress(context, _profile.wakeOnLANBroadcastAddress ?? ''); + if(_values[0]) { + _profile.wakeOnLANBroadcastAddress = _values[1]; + _profile.save(); + } + } + + Future _changeMACAddress() async { + List _values = await SettingsDialogs.editMACAddress(context, _profile.wakeOnLANMACAddress ?? ''); + if(_values[0]) { + _profile.wakeOnLANMACAddress = _values[1]; + _profile.save(); + } + } +} diff --git a/lib/modules/settings/routes/settings.dart b/lib/modules/settings/routes/settings.dart index 2a6e0d2e..cf457d68 100644 --- a/lib/modules/settings/routes/settings.dart +++ b/lib/modules/settings/routes/settings.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; -import '../../settings.dart'; +import 'package:lunasea/modules/settings.dart'; class Settings extends StatefulWidget { diff --git a/lib/modules/settings/routes/system.dart b/lib/modules/settings/routes/system.dart index 0a3a36b3..99f1add8 100644 --- a/lib/modules/settings/routes/system.dart +++ b/lib/modules/settings/routes/system.dart @@ -51,16 +51,13 @@ class _State extends State with AutomaticKeepAliveClientMixin { List get _system => [ LSHeader( text: 'System', - subtitle: 'System functions for LunaSea', + subtitle: 'System information and utilities for LunaSea', ), LSCardTile( title: LSTitle(text: 'Version: $_version ($_buildNumber)'), subtitle: LSSubtitle(text: 'View Recent Changes'), trailing: LSIconButton(icon: Icons.system_update), - onTap: () async { - List changes = await SettingsAPI.getChangelog(); - await LSDialogSettings.showChangelog(context, changes); - }, + onTap: () async => Navigator.of(context).pushNamed(SettingsSystemChangelog.ROUTE_NAME), ), LSCardTile( title: LSTitle(text: 'Licenses'), @@ -89,8 +86,8 @@ class _State extends State with AutomaticKeepAliveClientMixin { onTap: () async => await Navigator.of(context).pushNamed(SettingsSystemDonations.ROUTE_NAME), ), LSCardTile( - title: LSTitle(text: 'Feedback'), - subtitle: LSSubtitle(text: 'Request New Features & Feedback'), + title: LSTitle(text: 'Feedback Board'), + subtitle: LSSubtitle(text: 'Request New Features'), trailing: LSIconButton(icon: Icons.speaker_notes), onTap: () async => await Constants.URL_FEEDBACK.lsLinks_OpenLink(), ), diff --git a/lib/modules/settings/routes/system/changelog.dart b/lib/modules/settings/routes/system/changelog.dart new file mode 100644 index 00000000..576a8fd2 --- /dev/null +++ b/lib/modules/settings/routes/system/changelog.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/settings.dart'; + +class SettingsSystemChangelog extends StatefulWidget { + static const ROUTE_NAME = '/settings/system/changelog'; + + @override + State createState() => _State(); +} + +class _State extends State { + final GlobalKey _refreshKey = GlobalKey(); + Future _future; + List _changes = []; + + @override + void initState() { + super.initState(); + _refresh(); + } + + Future _refresh() async { + _changes = []; + if(mounted) setState(() => { _future = SettingsAPI.getChangelog() }); + } + + @override + Widget build(BuildContext context) => Scaffold( + appBar: _appBar, + body: _body, + ); + + Widget get _body => LSRefreshIndicator( + refreshKey: _refreshKey, + onRefresh: () => _refresh(), + child: FutureBuilder( + future: _future, + builder: (context, snapshot) { + switch(snapshot.connectionState) { + case ConnectionState.done: { + if(snapshot.hasError || snapshot.data == null) return LSErrorMessage(onTapHandler: () => _refresh()); + _changes = snapshot.data; + return _list; + } + case ConnectionState.none: + case ConnectionState.waiting: + case ConnectionState.active: + default: return LSLoading(); + } + }, + ), + ); + + Widget get _list => _changes.length == 0 + ? LSGenericMessage(text: 'No Changes Found') + : LSListViewBuilder( + itemCount: _changes.length, + itemBuilder: (context, index) => LSCardTile( + title: LSTitle(text: _changes[index]['version']), + subtitle: LSSubtitle(text: _changes[index]['date']), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: () async => Navigator.of(context).pushNamed( + SettingsSystemChangelogDetails.ROUTE_NAME, + arguments: SettingsSystemChangelogDetailsArguments(details: _changes[index]), + ), + ), + ); + + Widget get _appBar => LSAppBar(title: 'Changelog'); +} diff --git a/lib/modules/settings/routes/system/changelog_details.dart b/lib/modules/settings/routes/system/changelog_details.dart new file mode 100644 index 00000000..59794018 --- /dev/null +++ b/lib/modules/settings/routes/system/changelog_details.dart @@ -0,0 +1,118 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:lunasea/core.dart'; + +class SettingsSystemChangelogDetailsArguments { + final Map details; + + SettingsSystemChangelogDetailsArguments({ + @required this.details, + }); +} + +class SettingsSystemChangelogDetails extends StatefulWidget { + static const ROUTE_NAME = '/settings/system/changelog/details'; + + @override + State createState() => _State(); +} + +class _State extends State { + SettingsSystemChangelogDetailsArguments _arguments; + + @override + void initState() { + super.initState(); + SchedulerBinding.instance.addPostFrameCallback((_) => + setState(() => _arguments = ModalRoute.of(context).settings.arguments) + ); + } + + @override + Widget build(BuildContext context) => Scaffold( + appBar: _appBar, + body: _body, + ); + + Widget get _body => _arguments == null + ? null + : LSListView( + children: [ + LSHeader( + text: 'New', + subtitle: 'New features and modules added in ${_arguments.details['version']}.', + ), + ..._new, + LSHeader( + text: 'Tweaks', + subtitle: 'Tweaks and general changes implemented in ${_arguments.details['version']}.', + ), + ..._tweaks, + LSHeader( + text: 'Fixes', + subtitle: 'Issues caught and fixed in ${_arguments.details['version']}.', + ), + ..._fixes, + ], + padBottom: true, + ); + + List get _new => List.generate( + _arguments.details['new'].length, + (index) { + String title = _arguments.details['new'][index]; + int bracket = title.indexOf('] '); + return LSCardTile( + title: LSTitle(text: title.substring(bracket == -1 ? 0 : bracket+2)), + subtitle: LSSubtitle(text: 'Added: ${bracket == -1 ? title : title.substring(1, bracket)}'), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: () async => GlobalDialogs.textPreview(context, 'Change', title.substring( + bracket == -1 + ? 0 + : bracket+2, + ), + ), + ); + }, + ); + + List get _tweaks => List.generate( + _arguments.details['tweaks'].length, + (index) { + String title = _arguments.details['tweaks'][index]; + int bracket = title.indexOf('] '); + return LSCardTile( + title: LSTitle(text: title.substring(bracket == -1 ? 0 : bracket+2)), + subtitle: LSSubtitle(text: 'Tweaked: ${bracket == -1 ? title : title.substring(1, bracket)}'), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: () async => GlobalDialogs.textPreview(context, 'Change', title.substring( + bracket == -1 + ? 0 + : bracket+2, + ), + ), + ); + }, + ); + + List get _fixes => List.generate( + _arguments.details['fixes'].length, + (index) { + String title = _arguments.details['fixes'][index]; + int bracket = title.indexOf('] '); + return LSCardTile( + title: LSTitle(text: title.substring(bracket == -1 ? 0 : bracket+2)), + subtitle: LSSubtitle(text: 'Fixed: ${bracket == -1 ? title : title.substring(1, bracket)}'), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: () async => GlobalDialogs.textPreview(context, 'Change', title.substring( + bracket == -1 + ? 0 + : bracket+2, + ), + ), + ); + }, + ); + + Widget get _appBar => LSAppBar(title: _arguments == null ? 'Changelog' : _arguments.details['version']); +} diff --git a/lib/modules/settings/widgets/general/configuration/backup.dart b/lib/modules/settings/widgets/general/configuration/backup.dart index d6cc6fed..cc68b480 100644 --- a/lib/modules/settings/widgets/general/configuration/backup.dart +++ b/lib/modules/settings/widgets/general/configuration/backup.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/settings.dart'; class SettingsGeneralConfigurationBackupTile extends StatelessWidget { @override @@ -12,7 +13,7 @@ class SettingsGeneralConfigurationBackupTile extends StatelessWidget { Future _backup(BuildContext context) async { try { - List _values = await LSDialogSystem.showBackupConfigurationPrompt(context); + List _values = await SettingsDialogs.backupConfiguration(context); if(_values[0]) { String data = Export.export(); String encrypted = Encryption.encrypt(_values[1], data); diff --git a/lib/modules/settings/widgets/general/configuration/restore.dart b/lib/modules/settings/widgets/general/configuration/restore.dart index 5f250a9f..f38e7717 100644 --- a/lib/modules/settings/widgets/general/configuration/restore.dart +++ b/lib/modules/settings/widgets/general/configuration/restore.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/settings.dart'; class SettingsGeneralConfigurationRestoreTile extends StatelessWidget { @override @@ -18,7 +19,7 @@ class SettingsGeneralConfigurationRestoreTile extends StatelessWidget { if(file == null) return; if(file.path.endsWith('json')) { String data = await file.readAsString(); - List values = await LSDialogSystem.showEncryptionKeyPrompt(context); + List values = await SettingsDialogs.enterEncryptionKey(context); if(values[0]) { String _decrypted = Encryption.decrypt(values[1], data); if(_decrypted != Constants.ENCRYPTION_FAILURE) { diff --git a/lib/modules/settings/widgets/general/logs/clear_tile.dart b/lib/modules/settings/widgets/general/logs/clear_tile.dart index bee4b55a..5084bea6 100644 --- a/lib/modules/settings/widgets/general/logs/clear_tile.dart +++ b/lib/modules/settings/widgets/general/logs/clear_tile.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/settings.dart'; class SettingsGeneralClearLogsTile extends StatelessWidget { @override @@ -11,7 +12,7 @@ class SettingsGeneralClearLogsTile extends StatelessWidget { ); Future _clearLogs(BuildContext context) async { - List _values = await LSDialogSettings.clearLogs(context); + List _values = await SettingsDialogs.clearLogs(context); if(_values[0]) { Logger.clearLogs(); LSSnackBar(context: context, title: 'Logs Cleared', message: 'All recorded logs have been cleared', type: SNACKBAR_TYPE.success); diff --git a/lib/modules/settings/widgets/general/logs/export_tile.dart b/lib/modules/settings/widgets/general/logs/export_tile.dart index 1129a17f..d1aff097 100644 --- a/lib/modules/settings/widgets/general/logs/export_tile.dart +++ b/lib/modules/settings/widgets/general/logs/export_tile.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/settings.dart'; class SettingsGeneralExportLogsTile extends StatelessWidget { @override @@ -11,7 +12,7 @@ class SettingsGeneralExportLogsTile extends StatelessWidget { ); Future _exportLogs(BuildContext context) async { - List _values = await LSDialogSettings.exportLogs(context); + List _values = await SettingsDialogs.exportLogs(context); if(_values[0]) { Logger.exportLogs(); LSSnackBar(context: context, title: 'Exported Logs', message: 'Logs are located in the application directory', type: SNACKBAR_TYPE.success); diff --git a/lib/modules/settings/widgets/general/profile/add_tile.dart b/lib/modules/settings/widgets/general/profile/add_tile.dart index dba51d0c..06e306c3 100644 --- a/lib/modules/settings/widgets/general/profile/add_tile.dart +++ b/lib/modules/settings/widgets/general/profile/add_tile.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/settings.dart'; class SettingsGeneralAddProfileTile extends StatelessWidget { @override @@ -11,7 +12,7 @@ class SettingsGeneralAddProfileTile extends StatelessWidget { ); Future _addProfile(BuildContext context) async { - List _values = await LSDialogSettings.addProfile(context); + List _values = await SettingsDialogs.addProfile(context); if(_values[0]) { List profiles = Database.profilesBox.keys.map((x) => x.toString().toLowerCase()).toList(); if(profiles.contains(_values[1].toString().toLowerCase())) { diff --git a/lib/modules/settings/widgets/general/profile/delete_tile.dart b/lib/modules/settings/widgets/general/profile/delete_tile.dart index dd8cf071..b524ccff 100644 --- a/lib/modules/settings/widgets/general/profile/delete_tile.dart +++ b/lib/modules/settings/widgets/general/profile/delete_tile.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/settings.dart'; class SettingsGeneralDeleteProfileTile extends StatelessWidget { @override @@ -11,7 +12,7 @@ class SettingsGeneralDeleteProfileTile extends StatelessWidget { ); Future _deleteProfile(BuildContext context) async { - List _values = await LSDialogSettings.deleteProfile( + List _values = await SettingsDialogs.deleteProfile( context, Database.profilesBox.keys.map((x) => x as String).toList()..sort((a,b) => a.toLowerCase().compareTo(b.toLowerCase())), ); diff --git a/lib/modules/settings/widgets/general/profile/enabled_tile.dart b/lib/modules/settings/widgets/general/profile/enabled_tile.dart index a964bb6f..ea4aee8f 100644 --- a/lib/modules/settings/widgets/general/profile/enabled_tile.dart +++ b/lib/modules/settings/widgets/general/profile/enabled_tile.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/settings.dart'; class SettingsGeneralEnabledProfileTile extends StatelessWidget { @override @@ -14,7 +15,7 @@ class SettingsGeneralEnabledProfileTile extends StatelessWidget { ); Future _changeProfile(BuildContext context) async { - List values = await LSDialogSettings.changeProfile( + List values = await SettingsDialogs.enabledProfile( context, Database.profilesBox.keys.map((x) => x as String).toList()..sort((a,b) => a.toLowerCase().compareTo(b.toLowerCase())), ); diff --git a/lib/modules/settings/widgets/general/profile/rename_tile.dart b/lib/modules/settings/widgets/general/profile/rename_tile.dart index 648e4b95..748736f6 100644 --- a/lib/modules/settings/widgets/general/profile/rename_tile.dart +++ b/lib/modules/settings/widgets/general/profile/rename_tile.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/settings.dart'; class SettingsGeneralRenameProfileTile extends StatelessWidget { @override @@ -11,13 +12,13 @@ class SettingsGeneralRenameProfileTile extends StatelessWidget { ); Future _renameProfile(BuildContext context) async { - List _values = await LSDialogSettings.renameProfile( + List _values = await SettingsDialogs.renameProfile( context, Database.profilesBox.keys.map((x) => x as String).toList()..sort((a,b) => a.toLowerCase().compareTo(b.toLowerCase())), ); if(_values[0]) { String old = _values[1]; - _values = await LSDialogSettings.renameProfileSelected(context); + _values = await SettingsDialogs.renameProfileSelected(context); if(_values[0]) { if(Database.profilesBox.keys.contains(_values[1])) { LSSnackBar(context: context, title: 'Unable to Rename Profile', message: 'A profile with the name "${_values[1]}" already exists', type: SNACKBAR_TYPE.failure); diff --git a/lib/modules/settings/widgets/modules/calendar/start_date_tile.dart b/lib/modules/settings/widgets/modules/calendar/start_date_tile.dart index ed6beb10..50cc5d38 100644 --- a/lib/modules/settings/widgets/modules/calendar/start_date_tile.dart +++ b/lib/modules/settings/widgets/modules/calendar/start_date_tile.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/home.dart'; +import 'package:lunasea/modules/settings.dart'; class SettingsModulesCalendarStartingDateTile extends StatelessWidget { @override @@ -15,7 +16,7 @@ class SettingsModulesCalendarStartingDateTile extends StatelessWidget { ); Future _changeDate(BuildContext context) async { - List _values = await LSDialogSettings.homeChangeStartingDay(context); + List _values = await SettingsDialogs.editCalendarStartingDay(context); if(_values[0]) Database.lunaSeaBox.put( HomeDatabaseValue.CALENDAR_STARTING_DAY.key, _values[1], diff --git a/lib/modules/settings/widgets/modules/calendar/start_size_tile.dart b/lib/modules/settings/widgets/modules/calendar/start_size_tile.dart index f0896cec..cd17f1e2 100644 --- a/lib/modules/settings/widgets/modules/calendar/start_size_tile.dart +++ b/lib/modules/settings/widgets/modules/calendar/start_size_tile.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/home.dart'; +import 'package:lunasea/modules/settings.dart'; class SettingsModulesCalendarStartingSizeTile extends StatelessWidget { @override @@ -15,7 +16,7 @@ class SettingsModulesCalendarStartingSizeTile extends StatelessWidget { ); Future _changeSize(BuildContext context) async { - List _values = await LSDialogSettings.homeChangeStartingSize(context); + List _values = await SettingsDialogs.editCalendarStartingSize(context); if(_values[0]) Database.lunaSeaBox.put( HomeDatabaseValue.CALENDAR_STARTING_SIZE.key, _values[1], diff --git a/lib/modules/settings/widgets/modules/lunasea/browser_tile.dart b/lib/modules/settings/widgets/modules/lunasea/browser_tile.dart index 2849e182..63867c7d 100644 --- a/lib/modules/settings/widgets/modules/lunasea/browser_tile.dart +++ b/lib/modules/settings/widgets/modules/lunasea/browser_tile.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/settings.dart'; class SettingsModulesLunaSeaBrowserTile extends StatelessWidget { @override @@ -16,7 +17,7 @@ class SettingsModulesLunaSeaBrowserTile extends StatelessWidget { ); Future _changeBrowser(BuildContext context) async { - List _values = await LSDialogSettings.changeBrowser(context); + List _values = await SettingsDialogs.changeBrowser(context); if(_values[0]) Database.lunaSeaBox.put( LunaSeaDatabaseValue.SELECTED_BROWSER.key, _values[1], diff --git a/lib/modules/settings/widgets/system/reset_lunasea_tile.dart b/lib/modules/settings/widgets/system/reset_lunasea_tile.dart index a0ea6999..a25ee874 100644 --- a/lib/modules/settings/widgets/system/reset_lunasea_tile.dart +++ b/lib/modules/settings/widgets/system/reset_lunasea_tile.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/settings.dart'; class SettingsSystemClearConfigurationTile extends StatelessWidget { @override @@ -11,7 +12,7 @@ class SettingsSystemClearConfigurationTile extends StatelessWidget { ); Future _clear(BuildContext context) async { - List values = await LSDialogSystem.showClearConfigurationPrompt(context); + List values = await SettingsDialogs.clearLunaSeaConfiguration(context); if(values[0]) { Database.setDefaults(); LSSnackBar( diff --git a/lib/modules/sonarr/core.dart b/lib/modules/sonarr/core.dart index 49296795..1a5be69e 100644 --- a/lib/modules/sonarr/core.dart +++ b/lib/modules/sonarr/core.dart @@ -1,4 +1,5 @@ export './core/api.dart'; export './core/constants.dart'; export './core/database.dart'; +export './core/dialogs.dart'; export './core/state.dart'; diff --git a/lib/modules/sonarr/core/api/api.dart b/lib/modules/sonarr/core/api/api.dart index 56b7c0cd..230f1596 100644 --- a/lib/modules/sonarr/core/api/api.dart +++ b/lib/modules/sonarr/core/api/api.dart @@ -12,12 +12,14 @@ class SonarrAPI extends API { SonarrAPI._internal(this._values, this._dio); factory SonarrAPI.from(ProfileHiveObject profile) { + Map _headers = Map.from(profile.getSonarr()['headers']); Dio _client = Dio( BaseOptions( baseUrl: '${profile.getSonarr()['host']}/api/', queryParameters: { - 'apikey': profile.getSonarr()['key'], + if(profile.getSonarr()['key'] != '') 'apikey': profile.getSonarr()['key'], }, + headers: _headers, followRedirects: true, maxRedirects: 5, ), diff --git a/lib/modules/sonarr/core/constants.dart b/lib/modules/sonarr/core/constants.dart index a00fc1ba..35463c87 100644 --- a/lib/modules/sonarr/core/constants.dart +++ b/lib/modules/sonarr/core/constants.dart @@ -4,7 +4,9 @@ import 'package:lunasea/modules/sonarr.dart'; class SonarrConstants { SonarrConstants._(); - static const Map SERVICE_MAP = { + static const String MODULE_KEY = 'sonarr'; + + static const Map MODULE_MAP = { 'name': 'Sonarr', 'desc': 'Manage Television Series', 'icon': CustomIcons.television, diff --git a/lib/modules/sonarr/core/dialogs.dart b/lib/modules/sonarr/core/dialogs.dart new file mode 100644 index 00000000..20248ed5 --- /dev/null +++ b/lib/modules/sonarr/core/dialogs.dart @@ -0,0 +1,333 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:intl/intl.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrDialogs { + SonarrDialogs._(); + + static Future> downloadWarning(BuildContext context) async { + bool _flag = false; + + void _setValues(bool flag) { + _flag = flag; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Download Release', + buttons: [ + LSDialog.button( + text: 'Download', + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.textContent(text: 'Are you sure you want to download this release? It has been marked as a rejected release by Sonarr.') + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag]; + } + + static Future> deleteSeries(BuildContext context) async { + bool _flag = false; + bool _files = false; + + void _setValues(bool flag, bool files) { + _flag = flag; + _files = files; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Remove Series', + buttons: [ + LSDialog.button( + text: 'Remove + Files', + textColor: LSColors.red, + onPressed: () => _setValues(true, true), + ), + LSDialog.button( + text: 'Remove', + textColor: LSColors.red, + onPressed: () => _setValues(true, false), + ), + ], + content: [ + LSDialog.textContent(text: 'Are you sure you want to remove the series from Sonarr?'), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag, _files]; + } + + static Future> searchAllMissing(BuildContext context) async { + bool _flag = false; + + void _setValues(bool flag) { + _flag = flag; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Search All Missing', + buttons: [ + LSDialog.button( + text: 'Search', + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.textContent(text: 'Are you sure you want to search for all missing episodes?'), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag]; + } + + static Future> globalSettings(BuildContext context) async { + List> _options = [ + ['View Web GUI', Icons.language, 'web_gui'], + ['Update Library', Icons.autorenew, 'update_library'], + ['Run RSS Sync', Icons.rss_feed, 'rss_sync'], + ['Search All Missing', Icons.search, 'missing_search'], + ['Backup Database', Icons.save, 'backup'], + ]; + bool _flag = false; + String _value = ''; + + void _setValues(bool flag, String value) { + _flag = flag; + _value = value; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Sonarr Settings', + content: List.generate( + _options.length, + (index) => LSDialog.tile( + text: _options[index][0], + icon: _options[index][1], + iconColor: LSColors.list(index), + onTap: () => _setValues(true, _options[index][2]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _value]; + } + + static Future> editEpisode(BuildContext context, String title, bool monitored, bool canDelete) async { + List> _options = [ + monitored + ? ['Unmonitor Episode', Icons.turned_in_not, 'monitor_status'] + : ['Monitor Episode', Icons.turned_in, 'monitor_status'], + ['Automatic Search', Icons.search, 'search_automatic'], + ['Manual Search', Icons.youtube_searched_for, 'search_manual'], + if(canDelete) ['Delete File', Icons.delete, 'delete_file'], + ]; + bool _flag = false; + String _value = ''; + + void _setValues(bool flag, String value) { + _flag = flag; + _value = value; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: title, + content: List.generate( + _options.length, + (index) => LSDialog.tile( + text: _options[index][0], + icon: _options[index][1], + iconColor: LSColors.list(index), + onTap: () => _setValues(true, _options[index][2]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _value]; + } + + static Future> editSeries(BuildContext context, SonarrCatalogueData entry) async { + List> _options = [ + ['Edit Series', Icons.edit, 'edit_series'], + ['Refresh Series', Icons.refresh, 'refresh_series'], + ['Remove Series', Icons.delete, 'remove_series'], + ]; + bool _flag = false; + String _value = ''; + + void _setValues(bool flag, String value) { + _flag = flag; + _value = value; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: entry.title, + content: List.generate( + _options.length, + (index) => LSDialog.tile( + icon: _options[index][1], + iconColor: LSColors.list(index), + text: _options[index][0], + onTap: () => _setValues(true, _options[index][2]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _value]; + } + + static Future> editRootFolder(BuildContext context, List folders) async { + bool _flag = false; + SonarrRootFolder _folder; + + void _setValues(bool flag, SonarrRootFolder folder) { + _flag = flag; + _folder = folder; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Root Folder', + content: List.generate( + folders.length, + (index) => LSDialog.tile( + text: folders[index].path, + subtitle: LSDialog.richText( + children: [ + LSDialog.bolded(text: folders[index].freeSpace.lsBytes_BytesToString()), + ], + ), + icon: Icons.folder, + iconColor: LSColors.list(index), + onTap: () => _setValues(true, folders[index]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _folder]; + } + + static Future> editQualityProfile(BuildContext context, List qualities) async { + bool _flag = false; + SonarrQualityProfile _quality; + + void _setValues(bool flag, SonarrQualityProfile quality) { + _flag = flag; + _quality = quality; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Quality Profile', + content: List.generate( + qualities.length, + (index) => LSDialog.tile( + text: qualities[index].name, + icon: Icons.portrait, + iconColor: LSColors.list(index), + onTap: () => _setValues(true, qualities[index]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _quality]; + } + + static Future> editSeriesType(BuildContext context) async { + bool _flag = false; + SonarrSeriesType _type; + + void _setValues(bool flag, SonarrSeriesType type) { + _flag = flag; + _type = type; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Series Type', + content: List.generate( + SonarrConstants.SERIES_TYPES.length, + (index) => LSDialog.tile( + text: toBeginningOfSentenceCase(SonarrConstants.SERIES_TYPES[index].type), + icon: Icons.tab, + iconColor: LSColors.list(index), + onTap: () => _setValues(true, SonarrConstants.SERIES_TYPES[index]) + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _type]; + } + + static Future> searchEntireSeason(BuildContext context, int seasonNumber) async { + bool _flag = false; + + void _setValues(bool flag) { + _flag = flag; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Episode Search', + buttons: [ + LSDialog.button( + text: 'Search', + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.textContent( + text: seasonNumber == 0 + ? 'Search for all episodes in specials?' + : 'Search for all episodes in season $seasonNumber?', + ), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag]; + } + + static Future> deleteEpisodeFile(BuildContext context) async { + bool _flag = false; + + void _setValues(bool flag) { + _flag = flag; + Navigator.of(context).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Delete Episode File', + buttons: [ + LSDialog.button( + text: 'Delete', + textColor: LSColors.red, + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.textContent(text: 'Are you sure you want to delete this episode file?'), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag]; + } +} diff --git a/lib/modules/sonarr/core/state.dart b/lib/modules/sonarr/core/state.dart index 710b5844..d05542ef 100644 --- a/lib/modules/sonarr/core/state.dart +++ b/lib/modules/sonarr/core/state.dart @@ -66,4 +66,12 @@ class SonarrModel extends ChangeNotifier { _navigationIndex = navigationIndex; notifyListeners(); } + + int _seriesNavigationIndex = 1; + int get seriesNavigationIndex => _seriesNavigationIndex; + set seriesNavigationIndex(int seriesNavigationIndex) { + assert(seriesNavigationIndex != null); + _seriesNavigationIndex = seriesNavigationIndex; + notifyListeners(); + } } diff --git a/lib/modules/sonarr/routes/add_details.dart b/lib/modules/sonarr/routes/add_details.dart index 3de65caf..b3911c5d 100644 --- a/lib/modules/sonarr/routes/add_details.dart +++ b/lib/modules/sonarr/routes/add_details.dart @@ -140,6 +140,7 @@ class _State extends State { : _arguments.data.overview, uri: _arguments.data.posterURI ?? '', fallbackImage: 'assets/images/sonarr/noseriesposter.png', + headers: Database.currentProfileObject.getSonarr()['headers'], ), LSDivider(), ValueListenableBuilder( @@ -177,7 +178,7 @@ class _State extends State { subtitle: LSSubtitle(text: _rootfolder?.path ?? 'Unknown Root Folder'), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async { - List _values = await LSDialogSonarr.showEditRootFolderPrompt(context, _rootFolders); + List _values = await SonarrDialogs.editRootFolder(context, _rootFolders); if(_values[0]) box.put(SonarrDatabaseValue.ADD_ROOT_FOLDER.key, _values[1]); }, ); @@ -192,7 +193,7 @@ class _State extends State { subtitle: LSSubtitle(text: _profile?.name ?? 'Unknown Profile'), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async { - List _values = await LSDialogSonarr.showEditQualityProfilePrompt(context, _qualityProfiles); + List _values = await SonarrDialogs.editQualityProfile(context, _qualityProfiles); if(_values[0]) box.put(SonarrDatabaseValue.ADD_QUALITY_PROFILE.key, _values[1]); }, ); @@ -207,7 +208,7 @@ class _State extends State { subtitle: LSSubtitle(text: _type?.type?.lsLanguage_Capitalize() ?? 'Unknown Type'), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async { - List _values = await LSDialogSonarr.showEditSeriesTypePrompt(context); + List _values = await SonarrDialogs.editSeriesType(context); if(_values[0]) box.put(SonarrDatabaseValue.ADD_SERIES_TYPE.key, _values[1]); }, ); diff --git a/lib/modules/sonarr/routes/details_season.dart b/lib/modules/sonarr/routes/details_season.dart index e1da2cb3..e5c9e8cd 100644 --- a/lib/modules/sonarr/routes/details_season.dart +++ b/lib/modules/sonarr/routes/details_season.dart @@ -178,7 +178,7 @@ class _State extends State { await SonarrAPI.from(Database.currentProfileObject).searchEpisodes(_selected) .then((_) => LSSnackBar( context: context, - title: 'Searching for Selected Episodes', + title: 'Searching for Episodes', message: 'Searching for ${_selected.length} ${_selected.length == 1 ? 'episode' : 'episodes'}', )) .catchError((_) => LSSnackBar( @@ -193,7 +193,7 @@ class _State extends State { } Future _searchSeason(int season) async { - List _values = await LSDialogSonarr.showSearchSeasonPrompt(context, season); + List _values = await SonarrDialogs.searchEntireSeason(context, season); if(_values[0]) { SonarrAPI _api = SonarrAPI.from(Database.currentProfileObject); await _api.searchSeason(_arguments.seriesID, season) diff --git a/lib/modules/sonarr/routes/details_series.dart b/lib/modules/sonarr/routes/details_series.dart index 6b6acfbe..85f6eefa 100644 --- a/lib/modules/sonarr/routes/details_series.dart +++ b/lib/modules/sonarr/routes/details_series.dart @@ -22,18 +22,16 @@ class SonarrDetailsSeries extends StatefulWidget { class _State extends State { final GlobalKey _scaffoldKey = GlobalKey(); + final _pageController = PageController(initialPage: 1); SonarrDetailsSeriesArguments _arguments; bool _error = false; - final List _tabTitles = [ - 'Overview', - 'Seasons', - ]; @override void initState() { super.initState(); SchedulerBinding.instance.addPostFrameCallback((_) { _arguments = ModalRoute.of(context).settings.arguments; + Provider.of(context, listen: false).seriesNavigationIndex = 1; _fetch(); }); } @@ -51,7 +49,10 @@ class _State extends State { @override Widget build(BuildContext context) => Scaffold( key: _scaffoldKey, - appBar: _arguments == null || _arguments.data == null ? _appBar : null, + appBar: _appBar, + bottomNavigationBar: _arguments != null && _arguments.data != null + ? _bottomNavigationBar + : null, body: _arguments != null ? _arguments.data != null ? _body @@ -61,35 +62,34 @@ class _State extends State { : null, ); - Widget get _appBar => LSAppBar(title: 'Series Details'); - - Widget get _body => DefaultTabController( - length: _tabTitles.length, - initialIndex: 1, - child: LSSliverAppBarTabs( - title: _arguments.data.title, - backgroundURI: _arguments.data.fanartURI(highRes: true), - bottom: TabBar( - tabs: [ - for(int i =0; i<_tabTitles.length; i++) Tab( - child: Text(_tabTitles[i]), - ), - ], - ), - body: TabBarView( - children: [ - SonarrDetailsOverview(data: _arguments.data), - SonarrDetailsSeasonList(data: _arguments.data) - ], - ), - actions: [ + Widget get _appBar => LSAppBar( + title: _arguments == null || _arguments.data == null + ? 'Series Details' + : _arguments.data.title, + actions: _arguments == null || _arguments.data == null + ? null + : [ SonarrDetailsEditButton( data: _arguments.data, remove: (bool withData) => _removeCallback(withData), ), ], - ), ); + Widget get _bottomNavigationBar => SonarrSeriesNavigationBar(pageController: _pageController); + + List get _tabs => [ + SonarrDetailsOverview(data: _arguments.data), + SonarrDetailsSeasonList(data: _arguments.data), + ]; + + Widget get _body => PageView( + controller: _pageController, + children: _tabs, + onPageChanged: _onPageChanged, + ); + + void _onPageChanged(int index) => Provider.of(context, listen: false).seriesNavigationIndex = index; + Future _removeCallback(bool withData) async => Navigator.of(context).pop(['remove_series', withData]); } \ No newline at end of file diff --git a/lib/modules/sonarr/routes/edit_series.dart b/lib/modules/sonarr/routes/edit_series.dart index 37d12ecb..8f092709 100644 --- a/lib/modules/sonarr/routes/edit_series.dart +++ b/lib/modules/sonarr/routes/edit_series.dart @@ -137,17 +137,17 @@ class _State extends State { ); Future _changePath() async { - List _values = await LSDialogSystem.editText(context, 'Series Path', prefill: _path); + List _values = await GlobalDialogs.editText(context, 'Series Path', prefill: _path); if(_values[0] && mounted) setState(() => _path = _values[1]); } Future _changeProfile() async { - List _values = await LSDialogSonarr.showEditQualityProfilePrompt(context, _qualityProfiles); + List _values = await SonarrDialogs.editQualityProfile(context, _qualityProfiles); if(_values[0] && mounted) setState(() => _qualityProfile = _values[1]); } Future _changeType() async { - List _values = await LSDialogSonarr.showEditSeriesTypePrompt(context); + List _values = await SonarrDialogs.editSeriesType(context); if(_values[0] && mounted) setState(() => _seriesType = _values[1]); } diff --git a/lib/modules/sonarr/routes/search_details.dart b/lib/modules/sonarr/routes/search_details.dart index a721495b..0d0b036e 100644 --- a/lib/modules/sonarr/routes/search_details.dart +++ b/lib/modules/sonarr/routes/search_details.dart @@ -65,7 +65,7 @@ class _State extends State { title: LSTitle(text: 'Release Title'), subtitle: LSSubtitle(text: _arguments.data.title), trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: () async => LSDialogSystem.textPreview(context, 'Release Title', _arguments.data.title), + onTap: () async => GlobalDialogs.textPreview(context, 'Release Title', _arguments.data.title), ), LSContainerRow( children: [ @@ -158,42 +158,22 @@ class _State extends State { Future _warningAction() async => _showWarnings(); Future _downloadAction() async { - if(_arguments.data.approved) { - await _startDownload() - .then((_) => LSSnackBar( - context: context, - title: 'Downloading...', - message: _arguments.data.title, - type: SNACKBAR_TYPE.success, - showButton: true, - buttonText: 'Back', - buttonOnPressed: () => Navigator.of(context).popUntil(ModalRoute.withName(Sonarr.ROUTE_NAME)), - )) - .catchError((_) => LSSnackBar( - context: context, - title: 'Failed to Start Downloading', - message: Constants.CHECK_LOGS_MESSAGE, - type: SNACKBAR_TYPE.failure, - )); - } else { - List values = await LSDialogSonarr.showDownloadWarningPrompt(context); - if(values[0]) await _startDownload() - .then((_) => LSSnackBar( - context: context, - title: 'Downloading...', - message: _arguments.data.title, - type: SNACKBAR_TYPE.success, - showButton: true, - buttonText: 'Back', - buttonOnPressed: () => Navigator.of(context).popUntil(ModalRoute.withName(Sonarr.ROUTE_NAME)), - )) - .catchError((_) => LSSnackBar( - context: context, - title: 'Failed to Start Downloading', - message: Constants.CHECK_LOGS_MESSAGE, - type: SNACKBAR_TYPE.failure, - )); - } + await _startDownload() + .then((_) => LSSnackBar( + context: context, + title: 'Downloading...', + message: _arguments.data.title, + type: SNACKBAR_TYPE.success, + showButton: true, + buttonText: 'Back', + buttonOnPressed: () => Navigator.of(context).popUntil(ModalRoute.withName(Sonarr.ROUTE_NAME)), + )) + .catchError((_) => LSSnackBar( + context: context, + title: 'Failed to Start Downloading', + message: Constants.CHECK_LOGS_MESSAGE, + type: SNACKBAR_TYPE.failure, + )); } Future _showWarnings() async { @@ -201,7 +181,7 @@ class _State extends State { for(var i=0; i<_arguments.data.rejections.length; i++) { reject += '${i+1}. ${_arguments.data.rejections[i]}\n'; } - await LSDialogSystem.textPreview(context, 'Rejection Reasons', reject.substring(0, reject.length-1)); + await GlobalDialogs.textPreview(context, 'Rejection Reasons', reject.substring(0, reject.length-1)); } Future _startDownload() async { diff --git a/lib/modules/sonarr/routes/sonarr.dart b/lib/modules/sonarr/routes/sonarr.dart index faf7c496..c76c490d 100644 --- a/lib/modules/sonarr/routes/sonarr.dart +++ b/lib/modules/sonarr/routes/sonarr.dart @@ -29,18 +29,30 @@ class _State extends State { } @override - Widget build(BuildContext context) => ValueListenableBuilder( - valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]), - builder: (context, box, widget) { - if(_profileState != Database.currentProfileObject.toString()) _refreshProfile(); - return Scaffold( - key: _scaffoldKey, - body: _body, - drawer: _drawer, - appBar: _appBar, - bottomNavigationBar: _bottomNavigationBar, - ); + Widget build(BuildContext context) => WillPopScope( + onWillPop: () async { + if(_scaffoldKey.currentState.isDrawerOpen) { + //If the drawer is open, return true to close it + return true; + } else { + //If the drawer isn't open, open the drawer + _scaffoldKey.currentState.openDrawer(); + return false; + } }, + child: ValueListenableBuilder( + valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]), + builder: (context, box, widget) { + if(_profileState != Database.currentProfileObject.toString()) _refreshProfile(); + return Scaffold( + key: _scaffoldKey, + body: _body, + drawer: _drawer, + appBar: _appBar, + bottomNavigationBar: _bottomNavigationBar, + ); + }, + ), ); Widget get _drawer => LSDrawer(page: 'sonarr'); @@ -108,7 +120,7 @@ class _State extends State { } Future _handlePopup() async { - List values = await LSDialogSonarr.showSettingsPrompt(context); + List values = await SonarrDialogs.globalSettings(context); if(values[0]) switch(values[1]) { case 'web_gui': await _api.host?.toString()?.lsLinks_OpenLink(); break; case 'update_library': await _api.updateLibrary() @@ -124,7 +136,7 @@ class _State extends State { .catchError((_) => LSSnackBar(context: context, title: 'Failed to Backup Database', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure)); break; case 'missing_search': { - List values = await LSDialogSonarr.showSearchMissingPrompt(context); + List values = await SonarrDialogs.searchAllMissing(context); if(values[0]) await _api.searchAllMissing() .then((_) => LSSnackBar(context: context, title: 'Searching...', message: 'Search for all missing episodes')) .catchError((_) => LSSnackBar(context: context, title: 'Failed to Search', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure)); diff --git a/lib/modules/sonarr/widgets.dart b/lib/modules/sonarr/widgets.dart index be56327c..313b02a8 100644 --- a/lib/modules/sonarr/widgets.dart +++ b/lib/modules/sonarr/widgets.dart @@ -14,3 +14,4 @@ export './widgets/add_search_result_tile.dart'; export './widgets/episode_tile.dart'; export './widgets/search_result_tile.dart'; export './widgets/navigation_bar.dart'; +export './widgets/series_navigation_bar.dart'; diff --git a/lib/modules/sonarr/widgets/catalogue_tile.dart b/lib/modules/sonarr/widgets/catalogue_tile.dart index 06ea24aa..974cbb3b 100644 --- a/lib/modules/sonarr/widgets/catalogue_tile.dart +++ b/lib/modules/sonarr/widgets/catalogue_tile.dart @@ -44,6 +44,7 @@ class _State extends State { decoration: LSCardBackground( uri: widget.data.bannerURI(), darken: !widget.data.monitored, + headers: Database.currentProfileObject.getSonarr()['headers'], ), onTap: () async => _enterSeries(), onLongPress: () async => _handlePopup(), @@ -96,7 +97,7 @@ class _State extends State { } Future _handlePopup() async { - List values = await LSDialogSonarr.showEditSeriesPrompt(context, widget.data); + List values = await SonarrDialogs.editSeries(context, widget.data); if(values[0]) switch(values[1]) { case 'refresh_series': _refreshSeries(); break; case 'edit_series': _editSeries(); break; @@ -128,10 +129,10 @@ class _State extends State { } Future _removeSeries() async { final _api = SonarrAPI.from(Database.currentProfileObject); - List values = await LSDialogSonarr.showDeleteSeriesPrompt(context); + List values = await SonarrDialogs.deleteSeries(context); if(values[0]) { if(values[1]) { - values = await LSDialogSystem.deleteCatalogueWithFiles(context, widget.data.title); + values = await GlobalDialogs.deleteCatalogueWithFiles(context, widget.data.title); if(values[0]) { await _api.removeSeries(widget.data.seriesID, deleteFiles: true) .then((_) { diff --git a/lib/modules/sonarr/widgets/details_edit_button.dart b/lib/modules/sonarr/widgets/details_edit_button.dart index 11bf9fe7..da0efb39 100644 --- a/lib/modules/sonarr/widgets/details_edit_button.dart +++ b/lib/modules/sonarr/widgets/details_edit_button.dart @@ -25,7 +25,7 @@ class _State extends State { ); Future _handlePopup(BuildContext context) async { - List values = await LSDialogSonarr.showEditSeriesPrompt(context, widget.data); + List values = await SonarrDialogs.editSeries(context, widget.data); if(values[0]) switch(values[1]) { case 'refresh_series': _refreshSeries(context); break; case 'edit_series': _editSeries(context); break; @@ -58,10 +58,10 @@ class _State extends State { Future _removeSeries(BuildContext context) async { final _api = SonarrAPI.from(Database.currentProfileObject); - List values = await LSDialogSonarr.showDeleteSeriesPrompt(context); + List values = await SonarrDialogs.deleteSeries(context); if(values[0]) { if(values[1]) { - values = await LSDialogSystem.deleteCatalogueWithFiles(context, widget.data.title); + values = await GlobalDialogs.deleteCatalogueWithFiles(context, widget.data.title); if(values[0]) { await _api.removeSeries(widget.data.seriesID, deleteFiles: true) .then((_) => widget.remove(true)) diff --git a/lib/modules/sonarr/widgets/details_overview.dart b/lib/modules/sonarr/widgets/details_overview.dart index a3ac1010..18be72de 100644 --- a/lib/modules/sonarr/widgets/details_overview.dart +++ b/lib/modules/sonarr/widgets/details_overview.dart @@ -30,11 +30,12 @@ class _State extends State with AutomaticKeepAliveClientM : widget?.data?.overview, uri: widget?.data?.posterURI() ?? '', fallbackImage: 'assets/images/sonarr/noseriesposter.png', + headers: Database.currentProfileObject.getSonarr()['headers'], ), LSCardTile( title: LSTitle(text: 'Series Path', centerText: true), subtitle: LSSubtitle(text: widget?.data?.path ?? 'Unknown', centerText: true), - onTap: () => LSDialogSystem.textPreview(context, 'Series Path', widget?.data?.path ?? 'Unknown'), + onTap: () => GlobalDialogs.textPreview(context, 'Series Path', widget?.data?.path ?? 'Unknown'), ), LSContainerRow( children: [ @@ -143,7 +144,6 @@ class _State extends State with AutomaticKeepAliveClientM ], ), ], - padBottom: true, ); } } diff --git a/lib/modules/sonarr/widgets/details_season_list.dart b/lib/modules/sonarr/widgets/details_season_list.dart index 0e3ec837..c3bf2280 100644 --- a/lib/modules/sonarr/widgets/details_season_list.dart +++ b/lib/modules/sonarr/widgets/details_season_list.dart @@ -32,7 +32,6 @@ class _State extends State with AutomaticKeepAliveClien data: widget.data, index: index == 0 ? -1 : widget.data.seasonData.length-index, ), - padBottom: true, ); Widget get _empty => LSListView( diff --git a/lib/modules/sonarr/widgets/details_season_list_tile.dart b/lib/modules/sonarr/widgets/details_season_list_tile.dart index 5c0bd8db..427ef9ad 100644 --- a/lib/modules/sonarr/widgets/details_season_list_tile.dart +++ b/lib/modules/sonarr/widgets/details_season_list_tile.dart @@ -128,7 +128,7 @@ class _State extends State { ); Future _searchSeason(int season) async { - List _values = await LSDialogSonarr.showSearchSeasonPrompt(context, season); + List _values = await SonarrDialogs.searchEntireSeason(context, season); if(_values[0]) { SonarrAPI _api = SonarrAPI.from(Database.currentProfileObject); await _api.searchSeason(widget.data.seriesID, season) diff --git a/lib/modules/sonarr/widgets/episode_tile.dart b/lib/modules/sonarr/widgets/episode_tile.dart index a739470b..e38202a9 100644 --- a/lib/modules/sonarr/widgets/episode_tile.dart +++ b/lib/modules/sonarr/widgets/episode_tile.dart @@ -103,7 +103,7 @@ class _State extends State { ); Future _deleteFile() async { - List _values = await LSDialogSonarr.showDeleteFilePrompt(context); + List _values = await SonarrDialogs.deleteEpisodeFile(context); if(_values[0]) { SonarrAPI _api = SonarrAPI.from(Database.currentProfileObject); await _api.deleteEpisodeFile(widget.data.episodeFileID) @@ -152,7 +152,7 @@ class _State extends State { } Future _handlePopup() async { - List _values = await LSDialogSonarr.showEpisodeEditingPrompt(context, widget.data.episodeTitle, widget.data.isMonitored, widget.data.hasFile); + List _values = await SonarrDialogs.editEpisode(context, widget.data.episodeTitle, widget.data.isMonitored, widget.data.hasFile); if(_values[0]) switch(_values[1]) { case 'monitor_status': _toggleMonitoredStatus().catchError((_) {}); break; case 'search_automatic': await _automaticSearch().catchError((_) {}); break; diff --git a/lib/modules/sonarr/widgets/missing_tile.dart b/lib/modules/sonarr/widgets/missing_tile.dart index 2e420f40..ee70103a 100644 --- a/lib/modules/sonarr/widgets/missing_tile.dart +++ b/lib/modules/sonarr/widgets/missing_tile.dart @@ -57,7 +57,10 @@ class _State extends State { ), onTap: () async => _enterSeason(), onLongPress: () async => _enterSeries(), - decoration: LSCardBackground(uri: widget.data.bannerURI()), + decoration: LSCardBackground( + uri: widget.data.bannerURI(), + headers: Database.currentProfileObject.getSonarr()['headers'], + ), padContent: true, ); diff --git a/lib/modules/sonarr/widgets/search_result_tile.dart b/lib/modules/sonarr/widgets/search_result_tile.dart index 6bb5570f..8850bb44 100644 --- a/lib/modules/sonarr/widgets/search_result_tile.dart +++ b/lib/modules/sonarr/widgets/search_result_tile.dart @@ -86,7 +86,7 @@ class SonarrSearchResultTile extends StatelessWidget { Future _trailingLongPressed(BuildContext context) async { if(!data.approved) { - List values = await LSDialogSonarr.showDownloadWarningPrompt(context); + List values = await SonarrDialogs.downloadWarning(context); if(values[0]) await _startDownload() .then((_) => LSSnackBar( context: context, @@ -111,7 +111,7 @@ class SonarrSearchResultTile extends StatelessWidget { for(var i=0; i _startDownload() async { diff --git a/lib/modules/sonarr/widgets/series_navigation_bar.dart b/lib/modules/sonarr/widgets/series_navigation_bar.dart new file mode 100644 index 00000000..dec1b266 --- /dev/null +++ b/lib/modules/sonarr/widgets/series_navigation_bar.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import '../../sonarr.dart'; + +class SonarrSeriesNavigationBar extends StatefulWidget { + final PageController pageController; + + SonarrSeriesNavigationBar({ + Key key, + @required this.pageController, + }): super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State { + static const List _navbarTitles = [ + 'Overview', + 'Seasons', + ]; + + static const List _navbarIcons = [ + Icons.subject, + CustomIcons.television, + ]; + + @override + Widget build(BuildContext context) => Selector( + selector: (_, model) => model.seriesNavigationIndex, + builder: (context, index, _) => LSBottomNavigationBar( + index: index, + icons: _navbarIcons, + titles: _navbarTitles, + onTap: (index) async => await _navOnTap(index), + ), + ); + + Future _navOnTap(int index) async { + await widget.pageController.animateToPage( + index, + duration: Duration(milliseconds: Constants.UI_NAVIGATION_SPEED), + curve: Curves.easeOutSine, + ).then((_) => Provider.of(context, listen: false).seriesNavigationIndex = index); + } +} diff --git a/lib/modules/sonarr/widgets/upcoming_tile.dart b/lib/modules/sonarr/widgets/upcoming_tile.dart index a43116f0..aad4b723 100644 --- a/lib/modules/sonarr/widgets/upcoming_tile.dart +++ b/lib/modules/sonarr/widgets/upcoming_tile.dart @@ -57,7 +57,10 @@ class _State extends State { onTap: () async => _enterSeason(), onLongPress: () async => _enterSeries(), padContent: true, - decoration: LSCardBackground(uri: widget.data.bannerURI()), + decoration: LSCardBackground( + uri: widget.data.bannerURI(), + headers: Database.currentProfileObject.getSonarr()['headers'], + ), ); Future _enterSeason() async => await Navigator.of(context).pushNamed( diff --git a/lib/modules/wake_on_lan.dart b/lib/modules/wake_on_lan.dart new file mode 100644 index 00000000..d7f4ffce --- /dev/null +++ b/lib/modules/wake_on_lan.dart @@ -0,0 +1 @@ +export './wake_on_lan/core.dart'; diff --git a/lib/modules/wake_on_lan/core.dart b/lib/modules/wake_on_lan/core.dart new file mode 100644 index 00000000..07b28742 --- /dev/null +++ b/lib/modules/wake_on_lan/core.dart @@ -0,0 +1,2 @@ +export './core/api.dart'; +export './core/constants.dart'; diff --git a/lib/modules/wake_on_lan/core/api.dart b/lib/modules/wake_on_lan/core/api.dart new file mode 100644 index 00000000..5a695dbf --- /dev/null +++ b/lib/modules/wake_on_lan/core/api.dart @@ -0,0 +1,44 @@ +import 'dart:io'; +import 'package:lunasea/core.dart'; + +class WakeOnLANAPI extends API { + final Map _values; + + WakeOnLANAPI._internal(this._values); + factory WakeOnLANAPI.from(ProfileHiveObject profile) => WakeOnLANAPI._internal(profile.getWakeOnLAN()); + + void logWarning(String methodName, String text) => Logger.warning('package:lunasea/modules/wake_on_lan/core/api.dart', methodName, 'Wake on LAN: $text'); + void logError(String methodName, String text, Object error) => Logger.error('package:lunasea/modules/wake_on_lan/core/api.dart', methodName, 'Wake on LAN: $text', error, StackTrace.current); + + bool get enabled => _values['enabled']; + String get broadcastAddress => _values['broadcastAddress']; + String get macAddress => _values['MACAddress']; + + Future testConnection() async => true; + + Future wake() async { + try { + return await RawDatagramSocket.bind(InternetAddress.anyIPv4, 0) + .then((RawDatagramSocket socket) { + socket.broadcastEnabled = true; + socket.send(_packet, InternetAddress(broadcastAddress), 9); + socket.close(); + return true; + }); + } catch (error) { + logError('wake', 'Failed to wake machine', error); + return Future.error(error); + } + } + + List get _packet { + MacAddress _mac = MacAddress.from(macAddress); + List _macBytes = _mac.bytes; + List _data = []; + //Add 6 0xFF (255) bytes to the front + for(int i=0; i<6; i++) _data.add(0xFF); + //Add the Mac Address (bytes) 16 times to the packet + for(int j=0; j<16; j++) _data.addAll(_macBytes); + return _data; + } +} diff --git a/lib/modules/wake_on_lan/core/constants.dart b/lib/modules/wake_on_lan/core/constants.dart new file mode 100644 index 00000000..55f81d77 --- /dev/null +++ b/lib/modules/wake_on_lan/core/constants.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; + +class WakeOnLANConstants { + WakeOnLANConstants._(); + + static const MODULE_KEY = 'wake_on_lan'; + + static const Map MODULE_MAP = { + 'name': 'Wake on LAN', + 'desc': 'Wake a Sleeping Machine', + 'icon': Icons.settings_remote, + 'route': null, + }; +} diff --git a/pubspec.lock b/pubspec.lock index 91db0db5..5daaade1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,14 +7,14 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "3.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.39.4" + version: "0.39.8" archive: dependency: transitive description: @@ -28,14 +28,14 @@ packages: name: args url: "https://pub.dartlang.org" source: hosted - version: "1.5.2" + version: "1.6.0" asn1lib: dependency: transitive description: name: asn1lib url: "https://pub.dartlang.org" source: hosted - version: "0.5.8" + version: "0.6.4" async: dependency: transitive description: @@ -49,7 +49,7 @@ packages: name: build url: "https://pub.dartlang.org" source: hosted - version: "1.2.2" + version: "1.3.0" build_config: dependency: transitive description: @@ -70,21 +70,21 @@ packages: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "1.3.3" + version: "1.3.9" build_runner: dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.10.0" build_runner_core: dependency: transitive description: name: build_runner_core url: "https://pub.dartlang.org" source: hosted - version: "5.0.0" + version: "5.2.0" built_collection: dependency: transitive description: @@ -149,7 +149,7 @@ packages: source: hosted version: "2.1.1" convert: - dependency: transitive + dependency: "direct main" description: name: convert url: "https://pub.dartlang.org" @@ -161,7 +161,7 @@ packages: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.1.3" + version: "2.1.4" csslib: dependency: transitive description: @@ -210,7 +210,7 @@ packages: name: encrypt url: "https://pub.dartlang.org" source: hosted - version: "4.0.0" + version: "4.0.2" f_logs: dependency: "direct main" description: @@ -224,7 +224,7 @@ packages: name: file_picker url: "https://pub.dartlang.org" source: hosted - version: "1.6.2" + version: "1.9.0+1" fixnum: dependency: transitive description: @@ -246,13 +246,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.3" - flushbar: + flash: dependency: "direct main" description: - name: flushbar + name: flash url: "https://pub.dartlang.org" source: hosted - version: "1.10.0" + version: "1.3.1" flutter: dependency: "direct main" description: flutter @@ -271,7 +271,7 @@ packages: name: flutter_launcher_icons url: "https://pub.dartlang.org" source: hosted - version: "0.7.4" + version: "0.7.5" flutter_native_splash: dependency: "direct dev" description: @@ -312,6 +312,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.0" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" google_nav_bar: dependency: "direct main" description: @@ -346,7 +353,7 @@ packages: name: hive_generator url: "https://pub.dartlang.org" source: hosted - version: "0.7.0" + version: "0.7.0+2" html: dependency: transitive description: @@ -395,7 +402,7 @@ packages: name: in_app_purchase url: "https://pub.dartlang.org" source: hosted - version: "0.3.3" + version: "0.3.4" intl: dependency: "direct main" description: @@ -486,7 +493,7 @@ packages: name: package_info url: "https://pub.dartlang.org" source: hosted - version: "0.4.0+16" + version: "0.4.0+18" package_resolver: dependency: transitive description: @@ -521,7 +528,7 @@ packages: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "1.6.5" + version: "1.6.9" path_provider_macos: dependency: transitive description: @@ -591,7 +598,7 @@ packages: name: pointycastle url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.0.2" pool: dependency: transitive description: @@ -605,7 +612,7 @@ packages: name: provider url: "https://pub.dartlang.org" source: hosted - version: "4.0.5" + version: "4.1.2" pub_semver: dependency: transitive description: @@ -764,7 +771,7 @@ packages: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "5.4.2" + version: "5.4.10" url_launcher_macos: dependency: transitive description: @@ -844,4 +851,4 @@ packages: version: "2.2.0" sdks: dart: ">=2.7.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + flutter: ">=1.17.0 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 440ed984..bfe62de9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,47 +1,49 @@ name: lunasea description: Usenet Manager -version: 2.2.0+220003 +version: 2.3.0+230004 publish_to: 'none' environment: sdk: ">=2.7.0 <3.0.0" dependencies: + convert: ^2.1.1 cupertino_icons: ^0.1.3 dio: ^3.0.9 - encrypt: ^4.0.0 - file_picker: ^1.6.2 + encrypt: ^4.0.2 + file_picker: ^1.9.0+1 flare_flutter: ^2.0.3 - flushbar: ^1.10.0 + flash: ^1.3.1 flutter: sdk: flutter flutter_advanced_networkimage: ^0.7.0 flutter_sticky_header: ^0.4.2 flutter_svg: ^0.17.4 f_logs: ^1.2.2 + google_fonts: ^1.1.0 google_nav_bar: ^2.2.0 hive: ^1.4.1+1 hive_flutter: ^0.3.0+2 - in_app_purchase: ^0.3.3 + in_app_purchase: ^0.3.4 intl: ^0.16.1 - package_info: ^0.4.0+16 - path_provider: ^1.6.5 + package_info: ^0.4.0+18 + path_provider: ^1.6.9 percent_indicator: ^2.1.3 permission_handler: ^4.4.0+hotfix.2 - provider: ^4.0.5 + provider: ^4.1.2 stack_trace: ^1.9.3 table_calendar: ^2.2.3 tuple: ^1.0.3 - url_launcher: ^5.4.2 + url_launcher: ^5.4.10 validators: ^2.0.0+1 xml_parser: ^0.1.2 dev_dependencies: - build_runner: ^1.8.1 + build_runner: ^1.10.0 dapackages: ^1.1.1 - flutter_launcher_icons: ^0.7.4 + flutter_launcher_icons: ^0.7.5 flutter_native_splash: ^0.1.9 - hive_generator: ^0.7.0 + hive_generator: ^0.7.0+2 flutter: uses-material-design: true diff --git a/scripts/build_release b/scripts/build_release index 25307989..0286da10 100755 --- a/scripts/build_release +++ b/scripts/build_release @@ -8,4 +8,3 @@ flutter build ios # Build Android flutter build appbundle cp build/app/outputs/bundle/release/app-release.aab ./lunasea.aab - diff --git a/scripts/update_packages b/scripts/update_packages deleted file mode 100755 index e9a5b891..00000000 --- a/scripts/update_packages +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash - -cd .. -flutter pub pub run dapackages:dapackages.dart ./pubspec.yaml