From 983f5bf7ffe6a8fa091b966b8eb415d0536df408 Mon Sep 17 00:00:00 2001 From: Jagandeep Brar Date: Tue, 1 Dec 2020 12:50:06 -0600 Subject: [PATCH] [Release] v4.1.0+40100005 (#274) NEW - [Sonarr/Queue] Check and update the queue in the background every 60 seconds - [Sonarr/Queue] Ability to view and manage your queue - [Sonarr/Queue] Ability to delete an item in the queue - [Sonarr/Sorting] Ability to set the initial sorting direction alongside the sorting type for series and releases TWEAKS - [Radarr] Use Radarr v3 icon across the UI - [Settings/Dialogs] Small tweaks and additional notes for entering hosts and passwords - [Settings/Host Dialog] Add validator on host to ensure user adds http:// or https:// FIXES - [UI/Checkbox] Fix overly vibrant checkbox background - [Sonarr/Add] Fix possible grey screen when adding a show with no seasons yet - [Flutter] Upgraded Flutter & Packages --- CHANGELOG.md | 432 ++++++++++++++++++ README.md | 8 +- assets/CustomIcons.ttf | Bin 27356 -> 27676 bytes ios/Podfile.lock | 2 +- lib/core/dialogs.dart | 5 +- lib/core/theme.dart | 2 + lib/core/ui/icon/icon_custom.dart | 1 + lib/modules/radarr/core/constants.dart | 2 +- lib/modules/settings/core/dialogs.dart | 21 +- .../settings/modules/customization/route.dart | 6 +- .../modules/customization_sonarr/route.dart | 4 +- .../modules/customization_sonarr/widgets.dart | 2 + .../widgets/default_sorting_releases.dart | 6 +- .../default_sorting_releases_ascending.dart | 26 ++ .../widgets/default_sorting_series.dart | 6 +- .../default_sorting_series_ascending.dart | 26 ++ .../modules_nzbget/widgets/password_tile.dart | 5 + lib/modules/sonarr/core/database.dart | 6 + lib/modules/sonarr/core/dialogs.dart | 42 ++ lib/modules/sonarr/core/extensions.dart | 1 + .../core/extensions/sonarr_queue_record.dart | 33 ++ lib/modules/sonarr/core/state.dart | 65 ++- .../sonarr/core/types/monitor_status.dart | 1 + lib/modules/sonarr/modules/queue/route.dart | 60 ++- lib/modules/sonarr/modules/queue/widgets.dart | 1 + .../modules/queue/widgets/queue_tile.dart | 195 ++++++++ .../widgets/tile_episode.dart | 4 +- .../appbar_global_settings_action.dart | 8 +- pubspec.lock | 24 +- pubspec.yaml | 8 +- 30 files changed, 951 insertions(+), 51 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 lib/modules/settings/modules/customization_sonarr/widgets/default_sorting_releases_ascending.dart create mode 100644 lib/modules/settings/modules/customization_sonarr/widgets/default_sorting_series_ascending.dart create mode 100644 lib/modules/sonarr/core/extensions/sonarr_queue_record.dart create mode 100644 lib/modules/sonarr/modules/queue/widgets/queue_tile.dart diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..952b79eb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,432 @@ +# LunaSea Changelog + +## [Beta/TestFlight] v4.1.0 (40100005) + +#### NEW +- `[Sonarr/Queue]` Check and update the queue in the background every 60 seconds +- `[Sonarr/Queue]` Ability to view and manage your queue +- `[Sonarr/Queue]` Ability to delete an item in the queue +- `[Sonarr/Sorting]` Ability to set the initial sorting direction alongside the sorting type for series and releases + +#### TWEAKS +- `[Radarr]` Use Radarr v3 icon across the UI +- `[Settings/Dialogs]` Small tweaks and additional notes for entering hosts and passwords +- `[Settings/Host Dialog]` Add validator on host to ensure user adds http:// or https:// + +#### FIXES +- `[UI/Checkbox]` Fix overly vibrant checkbox background +- `[Sonarr/Add]` Fix possible grey screen when adding a show with no seasons yet +- `[Flutter]` Upgraded Flutter & Packages + +--- + +## [Beta/TestFlight] v4.1.0 (40100004) + +#### FIXES +- `[Sonarr/v2]` Fix grey screen when attempting to add a new series + +#### TWEAKS +- `[Packages]` Updated transitive Flutter packages + +--- + +## [Beta/TestFlight] v4.1.0 (40100003) + +#### NEW +- `[Sonarr]` Ability to manage your tags +- `[Sonarr/Add]` Ability to set tags when adding a new series +- `[Sonarr/Edit]` Ability to update tags when editing a series + +#### FIXES +- `[Routing]` Fixed some issues with routing +- `[Sonarr/Add]` After adding a series, hitting back will now show the series as added + +--- + +## [Beta/TestFlight] v4.1.0 (40100002) + +#### NEW +- `[Images]` Ability to set (or entirely disable) the opacity of the background image for cards +- `[Sonarr]` Ability to set the default sorting type for series and releases +- `[Tautulli/Activity]` Show the ETA for when the session will be completed +- `[Tautulli/Activity]` Show if the hardware transcoding is being used on the stream + +#### FIXES +- `[Networking]` Ensure that insecure (HTTP/80) connections are allowed at the platform-level +- `[Flutter]` Upgraded Flutter & Packages + +--- + +## [Beta/TestFlight] v4.1.0 (40100001) + +#### NEW +- `[Networking]` Strict TLS/SSL validation is now disabled globally +- `[Routing]` Hold the AppBar back button to pop back to the home page of the module +- `[Settings/Sonarr]` Toggle to enable Sonarr v3 features +- `[Sonarr]` Complete rewrite of Sonarr +- `[Sonarr]` State is now held across module switches +- `[Sonarr/Overview]` View tags applied to series +- `[Sonarr/Catalogue]` (v3 only) Ability to set and update language profile +- `[Sonarr/Catalogue]` Ability to view all, only monitored, or only unmonitored series +- `[Sonarr/Releases]` (v3 only) Ability to interactively search for season packs +- `[Sonarr/Releases]` Ability to view all, only approved, or only rejected releases + +#### TWEAKS +- `[Settings]` Removed all toggles for strict TLS +- `[Sonarr]` Many tweaks to Sonarr's design +- `[Sonarr/Add]` (v3 only) Tapping an already-added series will take you to the series page +- `[Sonarr/Add]` Automatically navigate to newly added series +- `[Sonarr/Series]` Toggling monitored state of series has now been moved to the edit prompt + +#### FIXES +- `[Images]` Images will now load for invalid/self-signed certificates +- `[Tautulli/Activity]` Fix grey screen for music activity +- `[TextField]` Fix TextField actions (cut, copy, paste, etc.) not showing +- `[Timestamps]` Fix 12:xx AM being shown as 00:xx AM +- `[Everything]` Additional small bug fixes + +--- + +## [Stable/Production] v4.0.0 (400102) + +#### NEW +- `[Module]` Tautulli support +- `[Calendar]` Ability to set how many days in the past and future to fetch calendar entries for +- `[Drawer]` Categorical folders are now optional +- `[Drawer]` If using folders, ability to set the initial expanded state +- `[Locale]` Ability to use 24 hour timestamps +- `[Quick Actions]` Ability to set upto four homescreen quick actions +- `[Search]` Ability to set custom headers for individual indexers +- `[Settings]` Complete overhaul of the settings +- `[Settings/Resources]` (iOS) Added link to join TestFlight + +#### TWEAKS +- `[Home/Calendar]` Separate calendar view type into its own preference +- `[Home/Calendar]` Show no modules enabled message when there are no automation modules enabled, not just no modules overall +- `[Home/Calendar]` Default past and future days is now 14 days for each +- `[Images]` Images are no longer cached to the device storage to heavily improve performance & reduce crashing on older/lower-end devices +- `[Images]` Fetch images at specific resolutions instead of full resolution to reduce network performance and decrease memory pressure +- `[Search]` Improved detection of adult categories +- `[Settings]` Settings now replaces the active module/route and shows the drawer on the base route +- `[Settings/System]` Removed clear image cache tile +- `[UI]` Added new loading animation design + +#### FIXES +- `[Flutter]` Updated packages & Flutter +- `[General]` Many small bug fixes +- `[LunaSea]` Many behind the scenes changes to reduce memory pressure +- `[LunaSea]` Heavy improvements and fixes to the navigation routing +- `[Settings/Logs]` Logs could be shown in reverse order +- `[Text]` Fixed "squished" text on iOS 14 and higher devices + +--- + +## [Stable/Production] v3.3.0 (330002) + +#### NEW +- `[Home/Calendar]` New "schedule" view +- `[Home/Modules]` Option to use module "brand colour" or the LunaSea list colours +- `[Search]` Option to hide adult categories +- `[-arr/Catalogue]` Ability to sort by date added +- `[-arr/Search]` Show the indexer/tracker on the collapsed tile + +#### TWEAKS +- None + +#### FIXES +- `[Sonarr]` Fixed multiple errors related to parsing fetched episode data +- `[Everything]` Multiple smaller crash/error fixes + +--- + +## [Stable/Production] v3.2.0 (320003) + +#### NEW +- `[Drawer]` Add the LunaSea logo to the header +- `[Logging]` Integrated Sentry, an open-source exception/crash log aggregation tool +- `[Settings/System]` Added toggle (under system -> advanced) to disable Sentry logging + +#### TWEAKS +- `[System]` Implemented new image caching backend +- `[APIs]` Improved exception logging +- `[Logging]` Improved on-device logger to have more useful stack traces + +#### FIXES +- `[-arr/Details]` Fixed crash related to state management +- `[radarr/Add]` Fixed crash when a searched movie does not have a minimum availability value available + +--- + +## [Stable/Production] v3.1.0 (310004) + +#### NEW + +- `[Settings/Resources]` Added Discord invite link for official server +- `[-arr]` Ability to search and enter interactive search from all upcoming and missing pages +- `[-arr/Add]` Ability to jump directly into newly added content from toast notification +- `[Radarr/Delete]` Ability to add to exclusion list when deleting content +- `[Clients]` Hide and show queue pause/start FAB on scroll +- `[Clients/History]` History tiles are now expandable in-line +- `[SABnzbd/Statistics]` Show both temporary and final storage space +- `[Calendar]` Ability to search/enter interactive search directly from calendar tiles + +#### TWEAKS + +- `[Development]` Updated Flutter packages + +#### FIXES + +- `[API/Host]` Correctly handle cases where host ends with a forward slash + +--- + +## [Stable/Production] v3.0.0 (300100) + +#### NEW + +- `[Search]` Releases are now shown as an expandable tile +- `[Search/Category Results]` Ability to sort and filter results +- `[Search/Search Results]` Ability to sort search results +- `[-rr/Catalogue]` Many additional sorting options +- `[-rr/Releases]` Releases are now shown as an expandable tile +- `[-rr/Releases]` Ability to sort, hide, and filter releases +- `[Sonarr/Add]` Ability to set initial episode monitoring state +- `[Sonarr/Episodes]` Episodes are now shown as an expandable tile +- `[Radarr/Releases]` Shows custom format tags +- `[Clients]` FAB icon animates between queue state +- `[Images]` Ability to clear network image cache +- `[UI/AMOLED Theme]` Optional setting to add subtle border + +#### TWEAKS + +- `[Changelog]` Removed integrated changelog, redirects to documentation changelog +- `[-rr]` Use the term "Interactive" instead of "Manual" for search +- `[-rr/Links]` Normalize size of images +- `[Sonarr/Upcoming]` Now shows dates in-line as a styled header +- `[Sonarr/Season View]` Now shows season number in-line as a styled header +- `[UI/Font]` Adjusted font size across entire application +- `[UI/Dropdowns]` Change dropdowns to popup menus +- `[UI]` Rounder radius around tiles, popups, etc. + +#### FIXES + +- `[Home/Calendar]` Row height is now statically set to prevent huge calendar on larger displays +- `[Sonarr/Seasons]` Fixed crash related to seasons with no statistics data for percentage of season completion +- `[Sonarr/Episodes]` Episode numbers greater than 100 could cause a line-break +- `[Sonarr/Episodes]` Fixed/safe-guarded layout crash on incomplete episode data received +- `[Sonarr/Queue]` Safe-guarded Sonarr queue results when results are null +- `[Snackbar]` Snackbar wouldn't adopt AMOLED styling when enabled +- `[State]` Fixed crashes caused by setting the state of an unmounted widget + +--- + +## [Stable/Production] v2.3.0 (230004) + +#### NEW + +- `[Module]` Wake on LAN support +- `[Settings/Modules]` Ability to set custom headers to be attached to module requests +- `[Settings/Changelog]` Changelog is now displayed as a route, not a dialog +- `[UX]` Hitting the back button on apex/primary routes will now open the drawer instead of closing the application +- `[UX]` Enable landscape mode for iPhones + +#### TWEAKS + +- `[-rr/Details]` Removed sliver AppBar and fanart as it caused unwanted scrolling interference (A revamped overview page in the future will bring back the fanart images) +- `[-rr/Details]` Switched from AppBar tabs to bottom navigation bar +- `[-rr/Release]` Do not show warning message when triggering a release download from details page on rejected release +- `[Dialogs]` Reimplemented all dialogs +- `[Dialogs]` Keyboard type is now set for all inputs +- `[Dialogs]` All inputs are now validated (to some extent) and can be submitted via the Keyboard + +#### FIXES + +- `[SnackBar]` SnackBar could cause UI locking when doing swipe-back gestures + +--- + +## [Stable/Production] v2.2.0 (220003) + +#### NEW + +- `[Donations]` Added donation IAPs +- `[Profiles]` Added rapid-profile switcher +- `[Settings]` Added license attributions +- `[Settings]` Added feedback link +- `[-rr/Add]` Search bar will now be autofocused +- `[SABnzbd]` Added statistics for individual servers +- `[NZBGet]` Option to use basic authentication instead of URL-encoded authentication + +#### TWEAKS + +- `[UI]` Removed custom letter spacing +- `[Settings]` Added subheaders + +#### FIXES + +- `[Database]` Cleanly exit on clean application exit to prevent corruption + +--- + +## [Stable/Production] v2.1.0 (66) + +#### NEW + +- `[Branding]` New icon & splash screen +- `[Links]` Ability to set which browser you open links in (iOS only) +- `[Home/Calendar]` Ability to set starting day of week +- `[Home/Calendar]` Ability to set starting calendar size +- `[Home/Calendar]` Ability to enable and disable specific modules that show up in the calendar +- `[Appearance/Theme]` Option to enable AMOLED black theme +- `[Platform]` LunaSea is now buildable & available for Android + +#### TWEAKS + +- `[Settings]` Redesign of settings page +- `[General]` Use the term "Modules" instead of "Services" +- `[-rr/Details]` Background image header/appbar is now collapsed by default +- `[-rr/Details]` Left align title to match the rest of LunaSea + +#### FIXES + +- `[Sonarr]` Toast for toggling season monitor state would show monitor status of the series, not the season +- `[Settings/Backup]` Backing up your configuration would always fail +- `[Search]` Would show an empty screen if you deleted all indexers while search is open + +--- + +## [Stable/Production] v2.0.1 (55) + +#### NEW + +- `[SABnzbd]` Ability to upload .rar & .gz archives +- `[-rr/Add]` All configurable values are now stored in cold database, persisting between sessions +- `[-rr/Add]` Root folders now also show available space for the folder +- `[Search]` When sending an NZB to a client, there is now an action in the toast to jump directly to the respective client +- `[Automation/Search]` When downloading a release manually, there is now an action in the toast to pop back to the main screen of the automation service +- `[Settings]` Ability to disable SSL/TLS validation for all services +- `[HTTP]` With the above, support for self-signed SSL certificates + +#### TWEAKS +- `[Descriptions]` Adjusted alignment and consistency when no summary is available +- `[Search]` Removed redundant “Sending” toast since it very quickly gets covered by the follow-up toast +- `[Automation]` Edit content prompt now shows full title of the content +- `[Clients]` Opening the edit menu now shows the full title of the job +- `[UI]` Tweaked design of content headers +- `[Settings]` Added headers to automation and client pages to separate regular and advanced configuration options + +#### FIXES + +- `[HTTP]` Safer handling of redirects +- `[Routes]` Make route changes asynchronous to help minimize stutters +- `[Radarr/Add]` Fixed wrong title for minimum availability card +- `[Restore Configuration]` Error message had typos +- `[UI]` Fixed uneven font sizing in some prompts +- `[NZBGet/SABnzbd]` Failure on uploading an NZB would show no error message + +--- + +## [Stable/Production] v2.0.0 (333) + +#### NEW + +- `[iOS]` Built for iOS 13.4 +- `[Indexers/Search]` Full support for browsing and searching Newznab-based indexers! +- `[Indexers/Search]` Support for NZBHydra2 +- `[Indexers/Search]` Download NZB files directly to your device +- `[Indexers/Search]` Send NZB files directly from the indexer to your client of choice +- `[-rr/Catalogue]` Catalogue search bar is now stickied +- `[-rr/History]` Ability to enter content +- `[-rr/Add]` More information (year, seasons, etc.) shown in result tiles +- `[NZBGet]` Ability to search through fetched history +- `[SABnzbd]` Ability to search through fetched history +- `[Settings/Configuration]` Ability to clear configuration +- `[Drawer]` Ability to switch profiles in navigation Drawer +- `[Snackbar]` New snackbar style, design, and formatting +- `[Everything]` All services now dynamically reload on profile changes + +#### TWEAKS + +- `[Everything]` Reimplemented... everything +- `[Everything]` Moved away from FABs (where possible) to buttons +- `[Navigation]` Ability to swipe left and right between bottom navigation tabs (except Settings) +- `[Navigation Bar]` Slightly redesigned +- `[Home/Summary]` Redesigned summary page +- `[Home/Summary]` Removed fetching of series count, movie count, and artist count +- `[Home/Calendar]` Now only shows 1 dot for each day instead of 3 +- `[Home/Calendar]` Highlights today as well as selected day +- `[Home/Calendar]` Now shows the day of the week on the calendar +- `[Home/Calendar]` Now defaults to showing 1 week (swipe down to show two weeks or one month) +- `[Settings]` No longer a need to save configuration settings, everything is saved automatically +- `[Search Bar]` "X" to clear search bars +- `[-rr/Catalogue]` Sorting now scrolls back to top +- `[-rr/Catalogue]` Moved hide unmonitored button to stickied search bar +- `[-rr/Add]` Selected profile, folders, etc. are stored in state until application is closed or profile is switched +- `[-rr/Images]` Images now show placeholder, transition to real content on load +- `[Sonarr/Episodes]` Multi-select episodes with single tap anywhere on tile, now highlighted for easier distinction +- `[SABnzbd]` Limit queue to first 100 results +- `[SABnzbd]` Lowered refresh rate to 2 seconds from 1 second +- `[NZBGet]` Limit queue to first 100 results +- `[NZBGet]` Lowered refresh rate to 2 seconds from 1 second +- `[UI]` Reduced elevation of UI in general + +#### FIXES + +- `[Sonarr/Search]` Searching for an entire season now executes a season search instead of individual episode searches +- `[Logs]` Fixed bug where logs could not load in some cases and always show "Logs Not Found" +- `[Radarr/Search]` Now correctly retains search state to prevent needless researching & API hits +- `[Configuration]` Deleting or renaming "default" profile could cause a new "default" profile to be created on next launch + +--- + +## [Stable/Production] v1.1.0 (44) + +#### NEW + +- `[Home]` Calendar view for upcoming -rr content +- `[Sonarr]` Ability to toggle single episode monitored status +- `[Sonarr]` Ability to delete file +- `[Radarr]` Split missing tab into missing and upcoming +- `[-rr]` Long-press tile on non-catalogue pages to view catalogue entry +- `[-rr]` Ability to delete files when deleting catalogue entries +- `[Documentation]` Added a link to WIP documentation + +#### FIXES + +- `[Prompts]` All prompts now use the correct colour ordering +- `[NZBGet]` Adjusted authentication method to interfere less with custom auth setups + +#### TWEAKS + +- `[Drawer]` Dropdown groups are open by default until more services are added +- `[Settings]` Minor tile shuffling and rewording +- `[Navigation]` New style for the navigation bars +- `[Navigation]` Added custom icons to navigation bars +- `[Releases]` Easier to differentiate the release's protocol + +--- + +## [Stable/Production] v1.0.0 (33) + +#### NEW + +- `[-rr]` When adding new content, checks if content is already in your library +- `[-rr]` Ability to sort catalogue by size of alphabetical +- `[Settings]` Ability to rename a profile +- `[SAB/NZBGet]` Ability to pause the queue for some amount of time +- `[NZBGet]` Ability to filter out successful history entries + +#### FIXES + +- `[Radarr]` Fetching quality profiles could fail +- `[SAB/NZBGet]` Percentage calculation could fail causing a grey screen +- `[SAB/NZBGet]` Connection error now cancels timer until manual refresh (cleaner logs) + +#### TWEAKS + +- `[Build]` Adjusted how build number is presented +- `[Sonarr]` Now shows size on disk on catalogue page +- `[Lidarr]` Now shows size on disk on catalogue page +- `[Settings]` Successful connection test now also triggers save +- `[Settings]` Save button is now more obvious/clear +- `[Settings]` Adjusted and added more tips when adding hosts diff --git a/README.md b/README.md index 21e8b338..b827011c 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,8 @@ LunaSea even comes with support for multiple instances of applications using pro 1. A MacOS Machine 2. An AppleID account (does not require developer account) -3. [Flutter Framework (Beta Channel)](https://flutter.dev/) -4. [XCode 11.4 or Higher](https://apps.apple.com/ca/app/xcode/id497799835?mt=12) +3. [Flutter Framework (Stable Channel)](https://flutter.dev/) +4. [XCode](https://apps.apple.com/ca/app/xcode/id497799835?mt=12) 5. [Developer Certificate Configured](https://github.com/LunaSeaApp/LunaSea/wiki/Setup-of-Development-Certificate) #### Developing @@ -72,8 +72,8 @@ LunaSea even comes with support for multiple instances of applications using pro #### Requirements -1. Android SDK/Android Studio Installed & Configured -2. [Flutter Framework (Beta Channel)](https://flutter.dev/) +1. [Android SDK/Android Studio Installed & Configured](https://developer.android.com/studio) +2. [Flutter Framework (Stable Channel)](https://flutter.dev/) 3. [Keystore Configured](https://github.com/LunaSeaApp/LunaSea/wiki/Configure-Keystore) #### Developing diff --git a/assets/CustomIcons.ttf b/assets/CustomIcons.ttf index 711aa65a45e1feeb73c1c91b36fc18ec2b082560..5d8b4fbf479a57debdf2c9f5b2c0216f84a81e7a 100644 GIT binary patch delta 664 zcmX9)O-vI}5T1E&f7tETO1JEi^0RGew-8MAfGrUM1SRo+F);=c5|j;pMX8WNOg+^D zC&K|+^u$3g#EUVc9JvxrOq6IyP&}X!(}QU+#)FsQY@Pp^@0<5t-j{vcIKb7m{8Ip2 z0f3Q-jI(f3s_X)=Gyspx%srfbwL5kUAnpM;+vzE1GWc=(7IBZrlW7{ry?lx49r9>8 zvy^*OrV8*oh2^>V2`9XNb(8kp^RHx_+yYCOj|p$2K6Ky7OpT5O`;Gu+mg&Oi!u;aW z=BL97z}*)_T3p*{>kk7Gm;C*c_QV}u?;r5++8u{Kx(rVc%K4aH0RH`s9Dysyz5-JJ8 z1JufvOK^LDVon@n7e1GE#I^A}2RMDlZ?m6p35I~)r=LNYOgd{e7_ORz*@_lk?22km9)>J9d|>`5@<@y=*MI{7Lj#Hmg07xMvJEUFmB6|T=ghQt1UBz$ONvb zy5FBij>xsBhF|OaO#2lj6x-?$6Ai(3rKx8CMO+VOvHA`1&0p2-^mwhY*f8Fx z)6tJj#yxh-1i_n(s@2~b>q%W}XwhVkA|opOmoYIOYL+Zrs{Rr+JK#mt9%*5+ZcI0_ zX3b~D#Cp$(T7AoI>Vx>4RbLi_fXYR0xIWPNTGqP_=JWPJX?S{wW4KtD9bgzKeVCO{ Nm9oyHlg-kX{sXf=i536= delta 356 zcmbPpgYnK)#tF*xQyCZ-dl(oP!jf|n3tq*k>}OzLN?~AN7D>;kOnWf@Y!w3o>mCM% zgb5j`i7Ap#IUfVnJ^*5~44?qJ4RbM&4U}h4$;d6Ks0A{B<~;!t4LSMAiHcXn&j9sT z0QpY2i4_G*y5e0xc?lq2AulmEH7Zuh?g0Zsnh(%Im4f`@l47gNatsWa1wchv6Za_F zD=;tu)dKPB%9XnD{5D@1xS3x7MHp`9Jyt-_|K~8jU{(V1IT)BgqCf!Br!ZNLaU-ML zVBoN4N=3*&hoZQFi0JLrM1=fiPn_JUem>Cr&pUQIB%#+Q=$O2LZ0EYum Aw*UYD diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 72761037..74a0c4dd 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -107,4 +107,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: d583357f6c19d22241654320250048fb085838b3 -COCOAPODS: 1.9.3 +COCOAPODS: 1.10.0 diff --git a/lib/core/dialogs.dart b/lib/core/dialogs.dart index 82ed87b9..808fcb80 100644 --- a/lib/core/dialogs.dart +++ b/lib/core/dialogs.dart @@ -5,7 +5,7 @@ import 'package:lunasea/core.dart'; class LunaDialogs { LunaDialogs._(); - static Future> editText(BuildContext context, String title, { String prefill = '' }) async { + static Future> editText(BuildContext context, String title, { String prefill = '', List extraText }) async { bool _flag = false; final _formKey = GlobalKey(); final _textController = TextEditingController()..text = prefill; @@ -27,6 +27,7 @@ class LunaDialogs { ), ], content: [ + if((extraText?.length ?? 0) != 0) LSDialog.richText(children: extraText), Form( key: _formKey, child: LSDialog.textFormInput( @@ -37,7 +38,7 @@ class LunaDialogs { ), ), ], - contentPadding: LSDialog.inputDialogContentPadding(), + contentPadding: (extraText?.length ?? 0) == 0 ? LSDialog.inputDialogContentPadding() : LSDialog.inputTextDialogContentPadding(), ); return [_flag, _textController.text]; } diff --git a/lib/core/theme.dart b/lib/core/theme.dart index 2b5fd2b6..02942f55 100644 --- a/lib/core/theme.dart +++ b/lib/core/theme.dart @@ -23,6 +23,7 @@ class LunaTheme { splashColor: LunaColours.splash, dialogBackgroundColor: LunaColours.secondary, dividerColor: LunaColours.accent.withAlpha(0), + toggleableActiveColor: LunaColours.accent, dividerTheme: DividerThemeData( color: LunaColours.accent, indent: 100.0, @@ -65,6 +66,7 @@ class LunaTheme { splashColor: LunaColours.splash, dialogBackgroundColor: Colors.black, dividerColor: LunaColours.accent.withAlpha(0), + toggleableActiveColor: LunaColours.accent, dividerTheme: DividerThemeData( color: LunaColours.accent, indent: 72.0, diff --git a/lib/core/ui/icon/icon_custom.dart b/lib/core/ui/icon/icon_custom.dart index b0eb76ba..9a9ac3e8 100644 --- a/lib/core/ui/icon/icon_custom.dart +++ b/lib/core/ui/icon/icon_custom.dart @@ -37,4 +37,5 @@ class CustomIcons { static const IconData discord = IconData(0xe81c, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData tautulli = IconData(0xe81d, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData plex = IconData(0xe81e, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData radarr = IconData(0xe81f, fontFamily: _kFontFam, fontPackage: _kFontPkg); } diff --git a/lib/modules/radarr/core/constants.dart b/lib/modules/radarr/core/constants.dart index da4c2f24..24741730 100644 --- a/lib/modules/radarr/core/constants.dart +++ b/lib/modules/radarr/core/constants.dart @@ -11,7 +11,7 @@ class RadarrConstants { name: 'Radarr', description: 'Manage Movies', settingsDescription: 'Configure Radarr', - icon: CustomIcons.movies, + icon: CustomIcons.radarr, route: '/radarr', color: Color(0xFFFEC333), ); diff --git a/lib/modules/settings/core/dialogs.dart b/lib/modules/settings/core/dialogs.dart index 3a9cb6c9..0b3b055f 100644 --- a/lib/modules/settings/core/dialogs.dart +++ b/lib/modules/settings/core/dialogs.dart @@ -31,14 +31,18 @@ class SettingsDialogs { 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.textSpanContent(text: '${Constants.TEXT_BULLET}\tThis is the URL in which you access the web GUI for the service\n'), + LSDialog.textSpanContent(text: '${Constants.TEXT_BULLET}\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.textSpanContent(text: '${Constants.TEXT_BULLET}\tDo not use '), + LSDialog.bolded(text: 'localhost'), + LSDialog.textSpanContent(text: ' or '), + LSDialog.bolded(text: '127.0.0.1\n'), + LSDialog.textSpanContent(text: '${Constants.TEXT_BULLET}\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'), + LSDialog.textSpanContent(text: '${Constants.TEXT_BULLET}\tTo add basic authentication, please set a custom header in the advanced section'), ], ), Form( @@ -48,7 +52,14 @@ class SettingsDialogs { title: title, keyboardType: TextInputType.url, onSubmitted: (_) => _setValues(true), - validator: (_) => null, + validator: (value) { + // Allow empty value + if(value == '') return null; + // Test for https:// or http:// + RegExp exp = RegExp(r"^(http|https)://", caseSensitive: false); + if(exp.hasMatch(value)) return null; + return 'Host must include http:// or https://'; + }, ), ), ], diff --git a/lib/modules/settings/modules/customization/route.dart b/lib/modules/settings/modules/customization/route.dart index 461d9400..b285c051 100644 --- a/lib/modules/settings/modules/customization/route.dart +++ b/lib/modules/settings/modules/customization/route.dart @@ -2,7 +2,7 @@ import 'dart:io'; import 'package:fluro/fluro.dart'; import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/settings.dart'; +import 'package:lunasea/modules.dart'; class SettingsCustomizationRouter { static const ROUTE_NAME = '/settings/customization'; @@ -107,9 +107,9 @@ class _State extends State<_SettingsCustomizationRoute> with AutomaticKeepAliveC onTap: () async => SettingsCustomizationLidarrRouter.navigateTo(context), ), LSCardTile( - title: LSTitle(text: 'Radarr'), + title: LSTitle(text: RadarrConstants.MODULE_MAP.name), subtitle: LSSubtitle(text: 'Radarr Customizations'), - trailing: LSIconButton(icon: CustomIcons.movies), + trailing: LSIconButton(icon: RadarrConstants.MODULE_MAP.icon), onTap: () async => SettingsCustomizationRadarrRouter.navigateTo(context), ), LSCardTile( diff --git a/lib/modules/settings/modules/customization_sonarr/route.dart b/lib/modules/settings/modules/customization_sonarr/route.dart index 19a49ae2..798517b9 100644 --- a/lib/modules/settings/modules/customization_sonarr/route.dart +++ b/lib/modules/settings/modules/customization_sonarr/route.dart @@ -67,9 +67,11 @@ class _State extends State<_SettingsCustomizationSonarrRoute> { List get _defaultSorting => [ LSHeader( text: 'Default Sorting', - subtitle: 'Choose the default sorting type for sortable lists', + subtitle: 'Choose default sorting options for sortable lists', ), SettingsCustomizationSonarrDefaultSortingReleasesTile(), + SettingsCustomizationSonarrDefaultSortingReleasesAscendingTile(), SettingsCustomizationSonarrDefaultSortingSeriesTile(), + SettingsCustomizationSonarrDefaultSortingSeriesAscendingTile(), ]; } diff --git a/lib/modules/settings/modules/customization_sonarr/widgets.dart b/lib/modules/settings/modules/customization_sonarr/widgets.dart index 0efadfce..a2f9c32e 100644 --- a/lib/modules/settings/modules/customization_sonarr/widgets.dart +++ b/lib/modules/settings/modules/customization_sonarr/widgets.dart @@ -1,4 +1,6 @@ export 'widgets/default_pages_home.dart'; export 'widgets/default_pages_series_details.dart'; +export 'widgets/default_sorting_releases_ascending.dart'; export 'widgets/default_sorting_releases.dart'; +export 'widgets/default_sorting_series_ascending.dart'; export 'widgets/default_sorting_series.dart'; diff --git a/lib/modules/settings/modules/customization_sonarr/widgets/default_sorting_releases.dart b/lib/modules/settings/modules/customization_sonarr/widgets/default_sorting_releases.dart index f6cddc2a..02057ef6 100644 --- a/lib/modules/settings/modules/customization_sonarr/widgets/default_sorting_releases.dart +++ b/lib/modules/settings/modules/customization_sonarr/widgets/default_sorting_releases.dart @@ -7,7 +7,7 @@ class SettingsCustomizationSonarrDefaultSortingReleasesTile extends StatelessWid Widget build(BuildContext context) => ValueListenableBuilder( valueListenable: Database.lunaSeaBox.listenable(keys: [SonarrDatabaseValue.DEFAULT_SORTING_RELEASES.key]), builder: (context, box, _) => LSCardTile( - title: LSTitle(text: 'Releases'), + title: LSTitle(text: 'Releases Category'), subtitle: LSSubtitle(text: (SonarrDatabaseValue.DEFAULT_SORTING_RELEASES.data as SonarrReleasesSorting).readable), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async => _defaultSorting(context), @@ -20,8 +20,8 @@ class SettingsCustomizationSonarrDefaultSortingReleasesTile extends StatelessWid if(_values[0]) { SonarrDatabaseValue.DEFAULT_SORTING_RELEASES.put(SonarrReleasesSorting.values[_values[1]]); // Reset the state of the sorting - context.read().releasesSortType = SonarrReleasesSorting.values[_values[1]]; - context.read().releasesSortAscending = true; + context.read().releasesSortType = SonarrDatabaseValue.DEFAULT_SORTING_RELEASES.data; + context.read().releasesSortAscending = SonarrDatabaseValue.DEFAULT_SORTING_RELEASES_ASCENDING.data; } } } diff --git a/lib/modules/settings/modules/customization_sonarr/widgets/default_sorting_releases_ascending.dart b/lib/modules/settings/modules/customization_sonarr/widgets/default_sorting_releases_ascending.dart new file mode 100644 index 00000000..f505f36a --- /dev/null +++ b/lib/modules/settings/modules/customization_sonarr/widgets/default_sorting_releases_ascending.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SettingsCustomizationSonarrDefaultSortingReleasesAscendingTile extends StatelessWidget { + @override + Widget build(BuildContext context) => ValueListenableBuilder( + valueListenable: Database.lunaSeaBox.listenable(keys: [SonarrDatabaseValue.DEFAULT_SORTING_RELEASES_ASCENDING.key]), + builder: (context, box, _) => LSCardTile( + title: LSTitle(text: 'Releases Sort Direction'), + subtitle: LSSubtitle(text: SonarrDatabaseValue.DEFAULT_SORTING_RELEASES_ASCENDING.data + ? 'Ascending' + : 'Descending' + ), + trailing: Switch( + value: SonarrDatabaseValue.DEFAULT_SORTING_RELEASES_ASCENDING.data, + onChanged: (value) { + SonarrDatabaseValue.DEFAULT_SORTING_RELEASES_ASCENDING.put(value); + // Reset the state of the sorting + context.read().releasesSortType = SonarrDatabaseValue.DEFAULT_SORTING_RELEASES.data; + context.read().releasesSortAscending = SonarrDatabaseValue.DEFAULT_SORTING_RELEASES_ASCENDING.data; + }, + ), + ), + ); +} diff --git a/lib/modules/settings/modules/customization_sonarr/widgets/default_sorting_series.dart b/lib/modules/settings/modules/customization_sonarr/widgets/default_sorting_series.dart index 76bfe078..6b71c43e 100644 --- a/lib/modules/settings/modules/customization_sonarr/widgets/default_sorting_series.dart +++ b/lib/modules/settings/modules/customization_sonarr/widgets/default_sorting_series.dart @@ -7,7 +7,7 @@ class SettingsCustomizationSonarrDefaultSortingSeriesTile extends StatelessWidge Widget build(BuildContext context) => ValueListenableBuilder( valueListenable: Database.lunaSeaBox.listenable(keys: [SonarrDatabaseValue.DEFAULT_SORTING_SERIES.key]), builder: (context, box, _) => LSCardTile( - title: LSTitle(text: 'Series'), + title: LSTitle(text: 'Series Category'), subtitle: LSSubtitle(text: (SonarrDatabaseValue.DEFAULT_SORTING_SERIES.data as SonarrSeriesSorting).readable), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async => _defaultSorting(context), @@ -20,8 +20,8 @@ class SettingsCustomizationSonarrDefaultSortingSeriesTile extends StatelessWidge if(_values[0]) { SonarrDatabaseValue.DEFAULT_SORTING_SERIES.put(SonarrSeriesSorting.values[_values[1]]); // Reset the state of the sorting - context.read().seriesSortType = SonarrSeriesSorting.values[_values[1]]; - context.read().seriesSortAscending = true; + context.read().seriesSortType = SonarrDatabaseValue.DEFAULT_SORTING_SERIES.data; + context.read().seriesSortAscending = SonarrDatabaseValue.DEFAULT_SORTING_SERIES_ASCENDING.data; } } } diff --git a/lib/modules/settings/modules/customization_sonarr/widgets/default_sorting_series_ascending.dart b/lib/modules/settings/modules/customization_sonarr/widgets/default_sorting_series_ascending.dart new file mode 100644 index 00000000..4c2be79d --- /dev/null +++ b/lib/modules/settings/modules/customization_sonarr/widgets/default_sorting_series_ascending.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SettingsCustomizationSonarrDefaultSortingSeriesAscendingTile extends StatelessWidget { + @override + Widget build(BuildContext context) => ValueListenableBuilder( + valueListenable: Database.lunaSeaBox.listenable(keys: [SonarrDatabaseValue.DEFAULT_SORTING_SERIES_ASCENDING.key]), + builder: (context, box, _) => LSCardTile( + title: LSTitle(text: 'Series Sort Direction'), + subtitle: LSSubtitle(text: SonarrDatabaseValue.DEFAULT_SORTING_SERIES_ASCENDING.data + ? 'Ascending' + : 'Descending' + ), + trailing: Switch( + value: SonarrDatabaseValue.DEFAULT_SORTING_SERIES_ASCENDING.data, + onChanged: (value) { + SonarrDatabaseValue.DEFAULT_SORTING_SERIES_ASCENDING.put(value); + // Reset the state of the sorting + context.read().seriesSortType = SonarrDatabaseValue.DEFAULT_SORTING_SERIES.data; + context.read().seriesSortAscending = SonarrDatabaseValue.DEFAULT_SORTING_SERIES_ASCENDING.data; + }, + ), + ), + ); +} diff --git a/lib/modules/settings/modules/modules_nzbget/widgets/password_tile.dart b/lib/modules/settings/modules/modules_nzbget/widgets/password_tile.dart index a321fc05..723532f3 100644 --- a/lib/modules/settings/modules/modules_nzbget/widgets/password_tile.dart +++ b/lib/modules/settings/modules/modules_nzbget/widgets/password_tile.dart @@ -19,6 +19,11 @@ class SettingsModulesNZBGetPasswordTile extends StatelessWidget { context, 'NZBGet Password', prefill: Database.currentProfileObject.nzbgetPass ?? '', + extraText: [ + LSDialog.textSpanContent(text: '${Constants.TEXT_BULLET}\tIf your password includes special characters, considering adding a '), + LSDialog.bolded(text: 'basic authentication'), + LSDialog.textSpanContent(text: ' header with your username and password instead for better support'), + ], ); if(_values[0]) { Database.currentProfileObject.nzbgetPass = _values[1]; diff --git a/lib/modules/sonarr/core/database.dart b/lib/modules/sonarr/core/database.dart index 1e58f9cb..03233073 100644 --- a/lib/modules/sonarr/core/database.dart +++ b/lib/modules/sonarr/core/database.dart @@ -28,6 +28,8 @@ enum SonarrDatabaseValue { ADD_SERIES_DEFAULT_ROOT_FOLDER, DEFAULT_SORTING_SERIES, DEFAULT_SORTING_RELEASES, + DEFAULT_SORTING_SERIES_ASCENDING, + DEFAULT_SORTING_RELEASES_ASCENDING, UPCOMING_FUTURE_DAYS, QUEUE_REFRESH_RATE, CONTENT_LOAD_LENGTH, @@ -50,6 +52,8 @@ extension SonarrDatabaseValueExtension on SonarrDatabaseValue { case SonarrDatabaseValue.ADD_SERIES_DEFAULT_ROOT_FOLDER: return 'SONARR_ADD_SERIES_DEFAULT_ROOT_FOLDER'; case SonarrDatabaseValue.DEFAULT_SORTING_SERIES: return 'SONARR_DEFAULT_SORTING_SERIES'; case SonarrDatabaseValue.DEFAULT_SORTING_RELEASES: return 'SONARR_DEFAULT_SORTING_RELEASES'; + case SonarrDatabaseValue.DEFAULT_SORTING_SERIES_ASCENDING: return 'SONARR_DEFAULT_SORTING_SERIES_ASCENDING'; + case SonarrDatabaseValue.DEFAULT_SORTING_RELEASES_ASCENDING: return 'SONARR_DEFAULT_SORTING_RELEASES_ASCENDING'; } throw Exception('key not found'); } @@ -71,6 +75,8 @@ extension SonarrDatabaseValueExtension on SonarrDatabaseValue { case SonarrDatabaseValue.ADD_SERIES_DEFAULT_ROOT_FOLDER : return _box.get(this.key, defaultValue: null); case SonarrDatabaseValue.DEFAULT_SORTING_SERIES: return _box.get(this.key, defaultValue: SonarrSeriesSorting.ALPHABETICAL); case SonarrDatabaseValue.DEFAULT_SORTING_RELEASES: return _box.get(this.key, defaultValue: SonarrReleasesSorting.WEIGHT); + case SonarrDatabaseValue.DEFAULT_SORTING_SERIES_ASCENDING: return _box.get(this.key, defaultValue: true); + case SonarrDatabaseValue.DEFAULT_SORTING_RELEASES_ASCENDING: return _box.get(this.key, defaultValue: true); } throw Exception('data not found'); } diff --git a/lib/modules/sonarr/core/dialogs.dart b/lib/modules/sonarr/core/dialogs.dart index 45cdd394..af3c9eed 100644 --- a/lib/modules/sonarr/core/dialogs.dart +++ b/lib/modules/sonarr/core/dialogs.dart @@ -557,6 +557,48 @@ class SonarrDialogs { return [_flag]; } + static Future> confirmDeleteQueue(BuildContext context) async { + bool _flag = false; + + void _setValues(bool flag) { + _flag = flag; + Navigator.of(context, rootNavigator: true).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Remove From Queue', + buttons: [ + LSDialog.button( + text: 'Remove', + textColor: LunaColours.red, + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.textContent(text: 'Are you sure you want to remove this from the queue?\n'), + Selector( + selector: (_, state) => state.removeQueueBlacklist, + builder: (context, value, text) => CheckboxListTile( + title: text, + value: value, + onChanged: (selected) => Provider.of(context, listen: false).removeQueueBlacklist = selected, + contentPadding: LSDialog.tileContentPadding(), + ), + child: Text( + 'Blacklist', + style: TextStyle( + fontSize: LSDialog.BODY_SIZE, + color: Colors.white, + ), + ), + ), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag]; + } + static Future> confirmDeleteEpisodeFile(BuildContext context) async { bool _flag = false; diff --git a/lib/modules/sonarr/core/extensions.dart b/lib/modules/sonarr/core/extensions.dart index d1aa20a3..fcd87321 100644 --- a/lib/modules/sonarr/core/extensions.dart +++ b/lib/modules/sonarr/core/extensions.dart @@ -1,6 +1,7 @@ export 'extensions/sonarr_calendar.dart'; export 'extensions/sonarr_episode.dart'; export 'extensions/sonarr_history_event_type.dart'; +export 'extensions/sonarr_queue_record.dart'; export 'extensions/sonarr_series_lookup.dart'; export 'extensions/sonarr_series_season.dart'; export 'extensions/sonarr_series.dart'; diff --git a/lib/modules/sonarr/core/extensions/sonarr_queue_record.dart b/lib/modules/sonarr/core/extensions/sonarr_queue_record.dart new file mode 100644 index 00000000..716bcaab --- /dev/null +++ b/lib/modules/sonarr/core/extensions/sonarr_queue_record.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +extension SonarrQueueRecordExtension on SonarrQueueRecord { + String get lunaStatusText { + switch(this?.status?.toLowerCase() ?? '') { + case 'delay': return 'Pending'; + case 'downloading': return 'Downloading'; + case 'completed': return 'Completed'; + case 'paused': return 'Paused'; + } + return this?.status ?? 'Unknown'; + } + + IconData get lunaStatusIcon { + switch(this?.status?.toLowerCase() ?? '') { + case 'delay': return Icons.schedule; + case 'downloading': return Icons.cloud_download; + case 'completed': return Icons.cloud_download; + case 'paused': return Icons.pause; + } + return Icons.help; + } + + Color get lunaStatusColor { + if(this?.status?.toLowerCase() == 'completed') switch(this?.trackedDownloadStatus?.toLowerCase()) { + case 'warning': return LunaColours.orange; + case 'ok': return LunaColours.purple; + } + return Colors.white; + } +} \ No newline at end of file diff --git a/lib/modules/sonarr/core/state.dart b/lib/modules/sonarr/core/state.dart index c1d5e730..f3cfd0ba 100644 --- a/lib/modules/sonarr/core/state.dart +++ b/lib/modules/sonarr/core/state.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:lunasea/core.dart'; import 'package:lunasea/modules/sonarr.dart'; @@ -5,10 +7,17 @@ class SonarrState extends LunaGlobalState { SonarrState() { reset(); } + + @override + void dispose() { + _getQueueTimer?.cancel(); + super.dispose(); + } @override void reset() { // Reset stored data + _queue = null; _series = null; _missing = null; _upcoming = null; @@ -23,6 +32,7 @@ class SonarrState extends LunaGlobalState { _releasesSearchQuery = ''; // Reinitialize resetProfile(); + resetQueue(); resetSeries(); resetUpcoming(); resetMissing(); @@ -78,6 +88,45 @@ class SonarrState extends LunaGlobalState { ) : null; } + + ///////////// + /// QUEUE /// + ///////////// + + /// Timer to handle refreshing queue data + Timer _getQueueTimer; + + /// Cancel the periodic timer + void createQueueTimer() => _getQueueTimer = Timer.periodic( + Duration(seconds: SonarrDatabaseValue.QUEUE_REFRESH_RATE.data), + (_) => queue = _api.queue.getQueue(), + ); + + /// Cancel the queue timer + void cancelQueueTimer() => _getQueueTimer?.cancel(); + + /// Storing queue data + Future> _queue; + Future> get queue => _queue; + set queue(Future> queue) { + assert(queue != null); + _queue = queue; + notifyListeners(); + } + + /// Reset the queue: + /// - Cancel timer & clear state of future + /// - Recreate timer (if enabled) + /// - Set initial state of the future + void resetQueue() { + cancelQueueTimer(); + _queue = null; + if(_api != null) { + _queue = _api.queue.getQueue(); + createQueueTimer(); + } + notifyListeners(); + } //////////////// /// EPISODES /// @@ -173,7 +222,7 @@ class SonarrState extends LunaGlobalState { notifyListeners(); } - bool _releasesSortAscending = true; + bool _releasesSortAscending = SonarrDatabaseValue.DEFAULT_SORTING_SERIES_ASCENDING.data; bool get releasesSortAscending => _releasesSortAscending; set releasesSortAscending(bool releasesSortAscending) { assert(releasesSortAscending != null); @@ -209,7 +258,7 @@ class SonarrState extends LunaGlobalState { notifyListeners(); } - bool _seriesSortAscending = true; + bool _seriesSortAscending = SonarrDatabaseValue.DEFAULT_SORTING_SERIES_ASCENDING.data; bool get seriesSortAscending => _seriesSortAscending; set seriesSortAscending(bool seriesSortAscending) { assert(seriesSortAscending != null); @@ -356,6 +405,18 @@ class SonarrState extends LunaGlobalState { notifyListeners(); } + //////////////////// + /// DELETE QUEUE /// + //////////////////// + + bool _removeQueueBlacklist = false; + bool get removeQueueBlacklist => _removeQueueBlacklist; + set removeQueueBlacklist(bool removeQueueBlacklist) { + assert(removeQueueBlacklist != null); + _removeQueueBlacklist = removeQueueBlacklist; + notifyListeners(); + } + ////////////// /// IMAGES /// ////////////// diff --git a/lib/modules/sonarr/core/types/monitor_status.dart b/lib/modules/sonarr/core/types/monitor_status.dart index 1f5e4702..92c955e3 100644 --- a/lib/modules/sonarr/core/types/monitor_status.dart +++ b/lib/modules/sonarr/core/types/monitor_status.dart @@ -35,6 +35,7 @@ extension SonarrMonitorStatusExtension on SonarrMonitorStatus { } void process(List season) { + if(season == null || season.length == 0) return; switch(this) { case SonarrMonitorStatus.ALL: _all(season); break; case SonarrMonitorStatus.MISSING: _missing(season); break; diff --git a/lib/modules/sonarr/modules/queue/route.dart b/lib/modules/sonarr/modules/queue/route.dart index 5e30b757..e5a6101d 100644 --- a/lib/modules/sonarr/modules/queue/route.dart +++ b/lib/modules/sonarr/modules/queue/route.dart @@ -1,6 +1,8 @@ import 'package:fluro/fluro.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; class SonarrQueueRouter { static const String ROUTE_NAME = '/sonarr/queue/list'; @@ -28,12 +30,24 @@ class _SonarrQueueRoute extends StatefulWidget { class _State extends State<_SonarrQueueRoute> { final GlobalKey _scaffoldKey = GlobalKey(); + final GlobalKey _refreshKey = GlobalKey(); + + @override + void initState() { + super.initState(); + SchedulerBinding.instance.scheduleFrameCallback((_) => _refresh()); + } + + Future _refresh() async { + context.read().resetQueue(); + await context.read().queue; + } @override Widget build(BuildContext context) => Scaffold( key: _scaffoldKey, appBar: _appBar, - body: LSGenericMessage(text: 'Coming Soon!'), + body: _body, ); Widget get _appBar => LunaAppBar( @@ -41,4 +55,48 @@ class _State extends State<_SonarrQueueRoute> { title: 'Queue', popUntil: '/sonarr', ); + + Widget get _body => LSRefreshIndicator( + refreshKey: _refreshKey, + onRefresh: _refresh, + child: FutureBuilder( + future: context.watch().queue, + builder: (context, AsyncSnapshot> snapshot) { + if(snapshot.hasError) { + if(snapshot.connectionState != ConnectionState.waiting) { + LunaLogger.error( + '_SonarrQueueRoute', + '_body', + 'Unable to fetch Sonarr queue', + snapshot.error, + null, + uploadToSentry: !(snapshot.error is DioError), + ); + } + return LSErrorMessage(onTapHandler: () async => _refreshKey.currentState.show()); + } + if(snapshot.hasData) return snapshot.data.length == 0 + ? _emptyQueue + : _queue(snapshot.data); + return LSLoader(); + }, + ), + ); + + Widget get _emptyQueue => LSGenericMessage( + text: 'Empty Queue', + buttonText: 'Refresh', + showButton: true, + onTapHandler: () async => _refreshKey.currentState.show(), + ); + + Widget _queue(List queue) => LSListView( + children: List.generate( + queue.length, + (index) => SonarrQueueQueueTile( + key: ObjectKey(queue[index].id), + record: queue[index], + ), + ), + ); } diff --git a/lib/modules/sonarr/modules/queue/widgets.dart b/lib/modules/sonarr/modules/queue/widgets.dart index e69de29b..f6ee9af5 100644 --- a/lib/modules/sonarr/modules/queue/widgets.dart +++ b/lib/modules/sonarr/modules/queue/widgets.dart @@ -0,0 +1 @@ +export 'widgets/queue_tile.dart'; diff --git a/lib/modules/sonarr/modules/queue/widgets/queue_tile.dart b/lib/modules/sonarr/modules/queue/widgets/queue_tile.dart new file mode 100644 index 00000000..d21f98a7 --- /dev/null +++ b/lib/modules/sonarr/modules/queue/widgets/queue_tile.dart @@ -0,0 +1,195 @@ +import 'package:flutter/material.dart'; +import 'package:expandable/expandable.dart'; +import 'package:intl/intl.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrQueueQueueTile extends StatelessWidget { + final SonarrQueueRecord record; + + SonarrQueueQueueTile({ + Key key, + @required this.record, + }) : super(key: key); + + final ExpandableController _controller = ExpandableController(); + + @override + Widget build(BuildContext context) => LSExpandable( + controller: _controller, + collapsed: _collapsed(context), + expanded: _expanded(context), + ); + + Widget _collapsed(BuildContext context) => LSCardTile( + title: LSTitle(text: record.title), + subtitle: RichText( + text: TextSpan( + style: TextStyle( + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + color: Colors.white70, + ), + children: [ + TextSpan(text: record.series.title), + TextSpan(text: '\nSeason ${record.episode.seasonNumber} ${Constants.TEXT_EMDASH} Episode ${record.episode.episodeNumber}\n'), + TextSpan( + text: record?.quality?.quality?.name ?? 'Unknown', + style: TextStyle( + fontWeight: FontWeight.w600, + color: LunaColours.accent, + ), + ), + ], + ), + maxLines: 3, + overflow: TextOverflow.fade, + softWrap: false, + ), + trailing: Padding( + child: LSIconButton(icon: record.lunaStatusIcon, color: record.lunaStatusColor), + padding: EdgeInsets.only(bottom: 14.0), + ), + padContent: true, + onTap: () => _controller.toggle(), + ); + + Widget _expanded(BuildContext context) => LSCard( + child: InkWell( + child: Row( + children: [ + Expanded( + child: Padding( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LSTitle(text: record.title, softWrap: true, maxLines: 12), + Padding( + child: Wrap( + direction: Axis.horizontal, + runSpacing: 10.0, + children: [ + LSTextHighlighted( + text: record.protocol.lsLanguage_Capitalize(), + bgColor: record.protocol == 'torrent' + ? LunaColours.purple + : LunaColours.blue, + ), + LSTextHighlighted( + text: record?.quality?.quality?.name ?? 'Unknown', + bgColor: LunaColours.accent, + ), + LSTextHighlighted( + text: record.lunaStatusText, + bgColor: LunaColours.blueGrey, + ), + if(record.trackedDownloadStatus != null && record.trackedDownloadStatus.isNotEmpty) LSTextHighlighted( + text: record.trackedDownloadStatus, + bgColor: LunaColours.blueGrey, + ), + ], + ), + padding: EdgeInsets.only(top: 8.0, bottom: 2.0), + ), + Padding( + child: RichText( + text: TextSpan( + style: TextStyle( + color: Colors.white70, + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + ), + children: [ + TextSpan( + text: record.episode.seasonNumber == 0 + ? 'Specials ${Constants.TEXT_EMDASH} Episode ${record.episode.episodeNumber}\n' + : 'Season ${record.episode.seasonNumber} ${Constants.TEXT_EMDASH} Episode ${record.episode.episodeNumber}\n', + style: TextStyle( + color: LunaColours.accent, + fontWeight: FontWeight.w600, + fontSize: Constants.UI_FONT_SIZE_STICKYHEADER, + ), + ), + TextSpan( + text: record.episode.airDateUtc == null + ? 'Unknown Date' + : DateFormat.yMMMMd().format(record.episode.airDateUtc.toLocal()), + style: TextStyle( + color: Colors.white, + ), + ), + TextSpan(text: '\n\n'), + TextSpan( + text: record.episode.overview == null || record.episode.overview.isEmpty + ? 'No overview is available.' + : record.episode.overview, + style: TextStyle( + fontStyle: FontStyle.italic, + ), + ) + ], + ), + ), + padding: EdgeInsets.only(top: 6.0, bottom: 10.0), + ), + Padding( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if(record?.statusMessages != null && (record?.statusMessages?.isNotEmpty ?? false)) Expanded( + child: LSButtonSlim( + text: 'Warnings', + backgroundColor: LunaColours.orange, + onTap: () async => LunaDialogs.textPreview(context, 'Warnings', record.statusMessages.fold('', (warnings, status) { + warnings += status.messages.fold('', (message, element) => message += '\n${Constants.TEXT_BULLET} $element'); + return warnings.trim(); + })), + margin: EdgeInsets.only(right: 6.0), + ), + ), + Expanded( + child: LSButtonSlim( + text: 'Delete', + backgroundColor: LunaColours.red, + onTap: () async => _deleteQueueRecord(context), + margin: record?.statusMessages != null && (record?.statusMessages?.isNotEmpty ?? false) + ? EdgeInsets.only(left: 6.0) + : EdgeInsets.zero, + ), + ), + ], + ), + padding: EdgeInsets.only(bottom: 2.0), + ), + ], + ), + padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 10.0), + ), + ) + ], + ), + borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), + onTap: () => _controller.toggle(), + ), + ); + + Future _deleteQueueRecord(BuildContext context) async { + List _values = await SonarrDialogs.confirmDeleteQueue(context); + if(_values[0]) context.read().api.queue.deleteQueue( + id: record.id, + blacklist: context.read().removeQueueBlacklist, + ).then((value) { + context.read().resetQueue(); + showLunaSuccessSnackBar(context: context, title: 'Removed From Queue', message: record.title); + }) + .catchError((error, stack) { + LunaLogger.error( + 'SonarrQueueQueueTile', + '_deleteQueueRecord', + 'Failed to remove item from queue: ${record.id}', + error, + stack, + uploadToSentry: !(error is DioError), + ); + showLunaErrorSnackBar(context: context, title: 'Failed to Delete From Queue'); + }); + } +} diff --git a/lib/modules/sonarr/modules/series_season_details/widgets/tile_episode.dart b/lib/modules/sonarr/modules/series_season_details/widgets/tile_episode.dart index 62191d80..f5c88eca 100644 --- a/lib/modules/sonarr/modules/series_season_details/widgets/tile_episode.dart +++ b/lib/modules/sonarr/modules/series_season_details/widgets/tile_episode.dart @@ -77,8 +77,8 @@ class _State extends State { children: [ TextSpan( text: widget.episode.seasonNumber == 0 - ? 'Specials / Episode ${widget.episode.episodeNumber}\n' - : 'Season ${widget.episode.seasonNumber} / Episode ${widget.episode.episodeNumber}\n', + ? 'Specials ${Constants.TEXT_EMDASH} Episode ${widget.episode.episodeNumber}\n' + : 'Season ${widget.episode.seasonNumber} ${Constants.TEXT_EMDASH} Episode ${widget.episode.episodeNumber}\n', style: TextStyle( color: LunaColours.accent, fontWeight: FontWeight.w600, diff --git a/lib/modules/sonarr/modules/sonarr/widgets/appbar_global_settings_action.dart b/lib/modules/sonarr/modules/sonarr/widgets/appbar_global_settings_action.dart index bcdf3f5a..e6766111 100644 --- a/lib/modules/sonarr/modules/sonarr/widgets/appbar_global_settings_action.dart +++ b/lib/modules/sonarr/modules/sonarr/widgets/appbar_global_settings_action.dart @@ -24,13 +24,7 @@ class SonarrAppBarGlobalSettingsAction extends StatelessWidget { Future _webGUI(BuildContext context) async => Provider.of(context, listen: false).host.lsLinks_OpenLink(); - Future _viewQueue(BuildContext context) async => LSSnackBar( - context: context, - title: 'Coming Soon!', - message: 'This feature has not yet been implemented', - type: SNACKBAR_TYPE.info, - ); - //Future _viewQueue(BuildContext context) async => SonarrQueueRouter.navigateTo(context); + Future _viewQueue(BuildContext context) async => SonarrQueueRouter.navigateTo(context); Future _manageTags(BuildContext context) async => SonarrTagsRouter.navigateTo(context); diff --git a/pubspec.lock b/pubspec.lock index 8db93407..7e97ef9f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -56,14 +56,14 @@ packages: name: build url: "https://pub.dartlang.org" source: hosted - version: "1.5.0" + version: "1.5.1" build_config: dependency: transitive description: name: build_config url: "https://pub.dartlang.org" source: hosted - version: "0.4.2" + version: "0.4.3" build_daemon: dependency: transitive description: @@ -84,14 +84,14 @@ packages: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "1.10.4" + version: "1.10.6" build_runner_core: dependency: transitive description: name: build_runner_core url: "https://pub.dartlang.org" source: hosted - version: "6.0.3" + version: "6.1.1" built_collection: dependency: transitive description: @@ -182,7 +182,7 @@ packages: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "1.3.9" + version: "1.3.10" dartx: dependency: transitive description: @@ -294,7 +294,7 @@ packages: name: fluro url: "https://pub.dartlang.org" source: hosted - version: "1.7.7" + version: "1.7.8" flutter: dependency: "direct main" description: flutter @@ -458,7 +458,7 @@ packages: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "3.1.0" + version: "3.1.1" logging: dependency: transitive description: @@ -500,14 +500,14 @@ packages: name: node_interop url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" node_io: dependency: transitive description: name: node_io url: "https://pub.dartlang.org" source: hosted - version: "1.1.1" + version: "1.2.0" package_config: dependency: transitive description: @@ -736,7 +736,7 @@ packages: name: sonarr url: "https://pub.dartlang.org" source: hosted - version: "0.0.2-pre.3" + version: "0.0.4" source_gen: dependency: transitive description: @@ -792,7 +792,7 @@ packages: name: table_calendar url: "https://pub.dartlang.org" source: hosted - version: "2.3.0" + version: "2.3.1" tautulli: dependency: "direct main" description: @@ -813,7 +813,7 @@ packages: name: time url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.4.0" timing: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 067da959..197ff60d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: lunasea description: Self Hosted Manager -version: 4.1.0+40100004 +version: 4.1.0+40100005 publish_to: 'none' environment: sdk: ">=2.7.0 <3.0.0" @@ -14,7 +14,7 @@ dependencies: file_picker: ^1.13.3 flare_flutter: ^2.0.6 flash: ^1.3.1 - fluro: ^1.7.7 + fluro: ^1.7.8 flutter: sdk: flutter flutter_spinkit: ^4.1.2+1 @@ -35,13 +35,13 @@ dependencies: quick_actions: ^0.4.0+10 sentry: ^3.0.1 stack_trace: ^1.9.6 - table_calendar: ^2.3.0 + table_calendar: ^2.3.1 tuple: ^1.0.3 url_launcher: ^5.7.10 uuid: ^2.2.2 xml_parser: ^0.1.2 # Comet.Tools Packages - sonarr: ^0.0.2-pre.3 + sonarr: ^0.0.4 tautulli: ^1.1.0 wake_on_lan: ^1.1.1+2