mirror of
https://github.com/jagandeepbrar/lunasea.git
synced 2026-08-30 20:24:25 +00:00
[Release] v5.0.0 (50000008) (#322)
NEW - [Webhooks] Show series, movie, or artist cover photos in incoming system notifications - [Webhooks/Radarr] Jump directly into the content by tapping the notification - [Webhooks/Sonarr] Jump directly into the content by tapping the notification - [-arr/Details] Added a button to the AppBar to jump directly into the edit page TWEAKS - [Radarr/Files] Moved the video block above the audio block FIXES - [AppBar/Dropdown] Vertical padding around title when the profile dropdown is visible was incorrect - [Radarr/Details] If the movie was not found, it would show an infinite loader, now shows a "No Movies Found" message - [Radarr/Releases] Changing the sorting or filter method would not scroll the list back to the top
This commit is contained in:
18
CHANGELOG.md
18
CHANGELOG.md
@@ -1,5 +1,23 @@
|
||||
# LunaSea Changelog
|
||||
|
||||
## v5.0.0 (50000008)
|
||||
|
||||
#### NEW
|
||||
- `[Webhooks]` Show series, movie, or artist cover photos in incoming system notifications
|
||||
- `[Webhooks/Radarr]` Jump directly into the content by tapping the notification
|
||||
- `[Webhooks/Sonarr]` Jump directly into the content by tapping the notification
|
||||
- `[-arr/Details]` Added a button to the AppBar to jump directly into the edit page
|
||||
|
||||
#### TWEAKS
|
||||
- `[Radarr/Files]` Moved the video block above the audio block
|
||||
|
||||
#### FIXES
|
||||
- `[AppBar/Dropdown]` Vertical padding around title when the profile dropdown is visible was incorrect
|
||||
- `[Radarr/Details]` If the movie was not found, it would show an infinite loader, now shows a "No Movies Found" message
|
||||
- `[Radarr/Releases]` Changing the sorting or filter method would not scroll the list back to the top
|
||||
|
||||
---
|
||||
|
||||
## v5.0.0 (50000007)
|
||||
|
||||
#### NEW
|
||||
|
||||
@@ -1,71 +1,58 @@
|
||||
{
|
||||
"motd": "This build brings some nice quality-of-life updates to usenet indexer search (with many more updates coming in the future).\n\nYou can now view the comments and download links in the expanded tile! Tapping the URL will open the URL, and long pressing the URL will copy it to your clipboard.\n\nThis build also improves the inkwell splashes across the UI, improves the UI when viewing rejection reasons for releases, merges Radarr's health check into the system status section, and fixes a few smaller bugs.",
|
||||
"motd": "This build introduces deeper support for notifications/webhooks (alpha) by supporting deep linking to content and displaying images within the system notifications!\n\nThis build also adds a new button the AppBar when you are within a series/movie/artist page. The pencil/edit icon now takes you directly to the edit page, and the three vertical dots will open the menu to refresh, delete, etc.",
|
||||
"version": "5.0.0",
|
||||
"build": "50000007",
|
||||
"build": "50000008",
|
||||
"new": [
|
||||
{
|
||||
"module": "Radarr/\nDisk Usage",
|
||||
"module": "Webhooks",
|
||||
"changes": [
|
||||
"Added a progress bar to better visualize disk usage"
|
||||
"Show series, movie, or artist cover photos in incoming system notifications"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "Search",
|
||||
"module": "Webhooks/\nRadarr",
|
||||
"changes": [
|
||||
"Show comment and download links in the expanded table"
|
||||
"Jump directly into the content by tapping the notification"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "Settings/\nSearch",
|
||||
"module": "Webhooks/\nSonarr",
|
||||
"changes": [
|
||||
"Ability to toggle off showing comment and download links"
|
||||
"Jump directly into the content by tapping the notification"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "UI/UX",
|
||||
"module": "-arr/\nDetails",
|
||||
"changes": [
|
||||
"If a URL is in a table, tap it to open the URL or long press to copy the URL"
|
||||
"Added a button to the AppBar to jump directly into the edit page"
|
||||
]
|
||||
}
|
||||
],
|
||||
"tweaks": [
|
||||
{
|
||||
"module": "-arr/\nReleases",
|
||||
"module": "Radarr/\nFiles",
|
||||
"changes": [
|
||||
"Improve the layout of the rejection reasons dialog"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "Radarr/\nHealth",
|
||||
"changes": [
|
||||
"Merged health check into system status"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "UI/UX",
|
||||
"changes": [
|
||||
"Added more descriptive tooltips to all popup menu buttons",
|
||||
"Adjusted the style of splash/highlight inking"
|
||||
"Moved the video block above the audio block"
|
||||
]
|
||||
}
|
||||
],
|
||||
"fixes": [
|
||||
{
|
||||
"module": "Images",
|
||||
"module": "AppBar/\nDropdown",
|
||||
"changes": [
|
||||
"Images could fail to load with specific headers"
|
||||
"Vertical padding around title when the profile dropdown is visible was incorrect"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "Radarr/\nDisk Usage",
|
||||
"module": "Radarr/\nDetails",
|
||||
"changes": [
|
||||
"An invalid total or free space value would cause a grey screen"
|
||||
"If the movie was not found it would show an infinite loader, now shows a \"No Movies Found\" message"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "Radarr/\nReleases",
|
||||
"changes": [
|
||||
"Clear button for the search bar would not show up"
|
||||
"Changing the sorting or filter method would not scroll the list back to the top"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
31
ios/ImageNotification/Info.plist
Normal file
31
ios/ImageNotification/Info.plist
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>ImageNotification</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.usernotifications.service</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>NotificationService</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
13
ios/ImageNotification/NotificationService.h
Normal file
13
ios/ImageNotification/NotificationService.h
Normal file
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// NotificationService.h
|
||||
// ImageNotification
|
||||
//
|
||||
// Created by Jagandeep Brar on 2021-02-25.
|
||||
// Copyright © 2021 The Chromium Authors. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
|
||||
@interface NotificationService : UNNotificationServiceExtension
|
||||
|
||||
@end
|
||||
34
ios/ImageNotification/NotificationService.m
Normal file
34
ios/ImageNotification/NotificationService.m
Normal file
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// NotificationService.m
|
||||
// ImageNotification
|
||||
//
|
||||
// Created by Jagandeep Brar on 2021-02-25.
|
||||
// Copyright © 2021 The Chromium Authors. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NotificationService.h"
|
||||
#import "FirebaseMessaging.h"
|
||||
|
||||
@interface NotificationService ()
|
||||
|
||||
@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
|
||||
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
|
||||
|
||||
@end
|
||||
|
||||
@implementation NotificationService
|
||||
|
||||
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
|
||||
self.contentHandler = contentHandler;
|
||||
self.bestAttemptContent = [request.content mutableCopy];
|
||||
|
||||
[[FIRMessaging extensionHelper] populateNotificationContent:self.bestAttemptContent withContentHandler:contentHandler];
|
||||
}
|
||||
|
||||
- (void)serviceExtensionTimeWillExpire {
|
||||
// Called just before the extension will be terminated by the system.
|
||||
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
|
||||
self.contentHandler(self.bestAttemptContent);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -51,3 +51,8 @@ post_install do |installer|
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
target 'ImageNotification' do
|
||||
use_frameworks!
|
||||
pod 'Firebase/Messaging'
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
PODS:
|
||||
- cloud_firestore (0.16.0):
|
||||
- cloud_firestore (0.16.0-1):
|
||||
- Firebase/Firestore (= 7.3.0)
|
||||
- firebase_core
|
||||
- Flutter
|
||||
@@ -54,14 +54,14 @@ PODS:
|
||||
- Firebase/Storage (7.3.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseStorage (~> 7.3.0)
|
||||
- firebase_auth (0.20.0-1):
|
||||
- firebase_auth (0.20.1):
|
||||
- Firebase/Auth (= 7.3.0)
|
||||
- firebase_core
|
||||
- Flutter
|
||||
- firebase_core (0.7.0):
|
||||
- Firebase/CoreOnly (= 7.3.0)
|
||||
- Flutter
|
||||
- firebase_messaging (8.0.0-dev.14):
|
||||
- firebase_messaging (8.0.0-dev.15):
|
||||
- Firebase/Messaging (= 7.3.0)
|
||||
- firebase_core
|
||||
- Flutter
|
||||
@@ -163,6 +163,7 @@ DEPENDENCIES:
|
||||
- cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`)
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||
- Firebase/Messaging
|
||||
- firebase_auth (from `.symlinks/plugins/firebase_auth/ios`)
|
||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
||||
@@ -246,15 +247,15 @@ CHECKOUT OPTIONS:
|
||||
:tag: 7.3.0
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
cloud_firestore: 433cf1aece50c727965355ae00e8dbeeceae66b1
|
||||
cloud_firestore: 357b92d4778d7715d76a393db5f89e7dcc9b7401
|
||||
connectivity_plus: f5e5e0b894ec1187bfab9816d6c2f034da8d46da
|
||||
DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d
|
||||
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
||||
file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1
|
||||
Firebase: 26223c695fe322633274198cb19dca8cb7e54416
|
||||
firebase_auth: b55e5136528dced982022ee073ed4b48f15c055c
|
||||
firebase_auth: 7220e26fc14a501a63093edc10443de6bca0d7e8
|
||||
firebase_core: 91b27774a52f41f8b58484a75edf71197ac01c59
|
||||
firebase_messaging: d039d8279704fc1e3d05e0a2bb2ce7dc29ee6be8
|
||||
firebase_messaging: caf0273c76e54f0a10dfe5048ac47b89e75bb78a
|
||||
firebase_storage: 554b8ce9192bfe0bd35f63da42d0d1f6ae819ccc
|
||||
FirebaseAuth: c224a0cf1afa0949bd5c7bfcf154b4f5ce8ddef2
|
||||
FirebaseCore: 4d3c72622ce0e2106aaa07bb4b2935ba2c370972
|
||||
@@ -284,6 +285,6 @@ SPEC CHECKSUMS:
|
||||
SwiftyGif: 5d4af95df24caf1c570dbbcb32a3b8a0763bc6d7
|
||||
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
|
||||
|
||||
PODFILE CHECKSUM: 9f0afed62614a65db8eaad79ca26aa6cd9cb4be2
|
||||
PODFILE CHECKSUM: d7f0a8efbe8010ddad9d00678781441d461a1402
|
||||
|
||||
COCOAPODS: 1.10.1
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
2A9501F70E4ADE3B795C4BD7 /* Pods_ImageNotification.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8B58106185CA1FE7B7C6832 /* Pods_ImageNotification.framework */; };
|
||||
3355779EEB7C12DC262232AF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D5A8F6AE14D184A58D8A7331 /* Pods_Runner.framework */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
@@ -16,8 +17,20 @@
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
9C2991242453FE3100B93AB2 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9C2991232453FE3100B93AB2 /* StoreKit.framework */; };
|
||||
9C5DBBF2257EB50400836D8D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9C5DBBF1257EB50400836D8D /* GoogleService-Info.plist */; };
|
||||
9C7092B325E77E470056AE0B /* NotificationService.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C7092B225E77E470056AE0B /* NotificationService.m */; };
|
||||
9C7092B725E77E470056AE0B /* ImageNotification.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 9C7092AF25E77E470056AE0B /* ImageNotification.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
9C7092B525E77E470056AE0B /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 9C7092AE25E77E470056AE0B;
|
||||
remoteInfo = ImageNotification;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
@@ -29,6 +42,17 @@
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
9C7092B825E77E470056AE0B /* Embed App Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
9C7092B725E77E470056AE0B /* ImageNotification.appex in Embed App Extensions */,
|
||||
);
|
||||
name = "Embed App Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@@ -49,9 +73,17 @@
|
||||
9C0A396F23E0F00900EC8127 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
|
||||
9C2991232453FE3100B93AB2 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
|
||||
9C5DBBF1257EB50400836D8D /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||
9C7092AF25E77E470056AE0B /* ImageNotification.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ImageNotification.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
9C7092B125E77E470056AE0B /* NotificationService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationService.h; sourceTree = "<group>"; };
|
||||
9C7092B225E77E470056AE0B /* NotificationService.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationService.m; sourceTree = "<group>"; };
|
||||
9C7092B425E77E470056AE0B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
9C9E2734C6D4DC2FF1815F04 /* Pods-ImageNotification.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.debug.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
A046209115F73D5EC348E242 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
D5A8F6AE14D184A58D8A7331 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DBA43D9DEB26304D56B42917 /* Pods-ImageNotification.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.profile.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
E2F8912DE8B8A6F72277E96C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
E8B58106185CA1FE7B7C6832 /* Pods_ImageNotification.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ImageNotification.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
FAED45593175690B5B62C40E /* Pods-ImageNotification.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.release.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.release.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -64,6 +96,14 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
9C7092AC25E77E470056AE0B /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
2A9501F70E4ADE3B795C4BD7 /* Pods_ImageNotification.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
@@ -84,6 +124,7 @@
|
||||
9C5DBBF1257EB50400836D8D /* GoogleService-Info.plist */,
|
||||
9740EEB11CF90186004384FC /* Flutter */,
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
9C7092B025E77E470056AE0B /* ImageNotification */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
F5D05D221249C73674C5DB0D /* Pods */,
|
||||
B52D05C72433A9F059B6393D /* Frameworks */,
|
||||
@@ -94,6 +135,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||
9C7092AF25E77E470056AE0B /* ImageNotification.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -122,11 +164,22 @@
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9C7092B025E77E470056AE0B /* ImageNotification */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9C7092B125E77E470056AE0B /* NotificationService.h */,
|
||||
9C7092B225E77E470056AE0B /* NotificationService.m */,
|
||||
9C7092B425E77E470056AE0B /* Info.plist */,
|
||||
);
|
||||
path = ImageNotification;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B52D05C72433A9F059B6393D /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9C2991232453FE3100B93AB2 /* StoreKit.framework */,
|
||||
D5A8F6AE14D184A58D8A7331 /* Pods_Runner.framework */,
|
||||
E8B58106185CA1FE7B7C6832 /* Pods_ImageNotification.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@@ -137,6 +190,9 @@
|
||||
E2F8912DE8B8A6F72277E96C /* Pods-Runner.debug.xcconfig */,
|
||||
7EF2984A57FD1E0C98A7277A /* Pods-Runner.release.xcconfig */,
|
||||
A046209115F73D5EC348E242 /* Pods-Runner.profile.xcconfig */,
|
||||
9C9E2734C6D4DC2FF1815F04 /* Pods-ImageNotification.debug.xcconfig */,
|
||||
FAED45593175690B5B62C40E /* Pods-ImageNotification.release.xcconfig */,
|
||||
DBA43D9DEB26304D56B42917 /* Pods-ImageNotification.profile.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
@@ -157,16 +213,36 @@
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
FB187DB152E6AC8A61142056 /* [CP] Embed Pods Frameworks */,
|
||||
3BA7760032A55E21F5FE79FC /* [CP] Copy Pods Resources */,
|
||||
9C7092B825E77E470056AE0B /* Embed App Extensions */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
9C7092B625E77E470056AE0B /* PBXTargetDependency */,
|
||||
);
|
||||
name = Runner;
|
||||
productName = Runner;
|
||||
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
9C7092AE25E77E470056AE0B /* ImageNotification */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 9C7092BC25E77E470056AE0B /* Build configuration list for PBXNativeTarget "ImageNotification" */;
|
||||
buildPhases = (
|
||||
BBE8B4F04EBFF199FEB18CC2 /* [CP] Check Pods Manifest.lock */,
|
||||
9C7092AB25E77E470056AE0B /* Sources */,
|
||||
9C7092AC25E77E470056AE0B /* Frameworks */,
|
||||
9C7092AD25E77E470056AE0B /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = ImageNotification;
|
||||
productName = ImageNotification;
|
||||
productReference = 9C7092AF25E77E470056AE0B /* ImageNotification.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
@@ -183,6 +259,11 @@
|
||||
LastSwiftMigration = 1100;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
9C7092AE25E77E470056AE0B = {
|
||||
CreatedOnToolsVersion = 12.4;
|
||||
DevelopmentTeam = VPH33JQH4R;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||
@@ -199,6 +280,7 @@
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
97C146ED1CF9000F007C117D /* Runner */,
|
||||
9C7092AE25E77E470056AE0B /* ImageNotification */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -216,6 +298,13 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
9C7092AD25E77E470056AE0B /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
@@ -287,6 +376,28 @@
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
BBE8B4F04EBFF199FEB18CC2 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-ImageNotification-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
FB187DB152E6AC8A61142056 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -371,8 +482,24 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
9C7092AB25E77E470056AE0B /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9C7092B325E77E470056AE0B /* NotificationService.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
9C7092B625E77E470056AE0B /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 9C7092AE25E77E470056AE0B /* ImageNotification */;
|
||||
targetProxy = 9C7092B525E77E470056AE0B /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
@@ -646,6 +773,88 @@
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
9C7092B925E77E470056AE0B /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9C9E2734C6D4DC2FF1815F04 /* Pods-ImageNotification.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = VPH33JQH4R;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = ImageNotification/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.lunasea.lunasea.ImageNotification;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
9C7092BA25E77E470056AE0B /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = FAED45593175690B5B62C40E /* Pods-ImageNotification.release.xcconfig */;
|
||||
buildSettings = {
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = VPH33JQH4R;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = ImageNotification/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.lunasea.lunasea.ImageNotification;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
9C7092BB25E77E470056AE0B /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = DBA43D9DEB26304D56B42917 /* Pods-ImageNotification.profile.xcconfig */;
|
||||
buildSettings = {
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = VPH33JQH4R;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = ImageNotification/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.lunasea.lunasea.ImageNotification;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
@@ -669,6 +878,16 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
9C7092BC25E77E470056AE0B /* Build configuration list for PBXNativeTarget "ImageNotification" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
9C7092B925E77E470056AE0B /* Debug */,
|
||||
9C7092BA25E77E470056AE0B /* Release */,
|
||||
9C7092BB25E77E470056AE0B /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
|
||||
@@ -177,14 +177,14 @@ class _State extends State<LunaAppBar> {
|
||||
|
||||
Widget _sharedLeading(BuildContext context) {
|
||||
if(widget.useDrawer) return IconButton(
|
||||
icon: Icon(Icons.menu),
|
||||
icon: Icon(Icons.menu_rounded),
|
||||
onPressed: () async {
|
||||
HapticFeedback.lightImpact();
|
||||
if(Scaffold.of(context).hasDrawer) Scaffold.of(context).openDrawer();
|
||||
},
|
||||
);
|
||||
return InkWell(
|
||||
child: Icon(Icons.arrow_back_ios),
|
||||
child: Icon(Icons.arrow_back_ios_rounded),
|
||||
onTap: () async {
|
||||
HapticFeedback.lightImpact();
|
||||
Navigator.of(context).lunaSafetyPop();
|
||||
@@ -229,12 +229,13 @@ class _State extends State<LunaAppBar> {
|
||||
tooltip: 'Change Profiles',
|
||||
child: Wrap(
|
||||
direction: Axis.horizontal,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
widget.title,
|
||||
style: TextStyle(fontSize: Constants.UI_FONT_SIZE_HEADER),
|
||||
),
|
||||
LSIcon(icon: Icons.arrow_drop_down),
|
||||
Icon(Icons.arrow_drop_down),
|
||||
],
|
||||
),
|
||||
onSelected: (result) {
|
||||
|
||||
@@ -2,3 +2,4 @@ export 'models/hive_objects.dart';
|
||||
export 'models/router.dart';
|
||||
export 'models/state.dart';
|
||||
export 'models/types.dart';
|
||||
export 'models/webhooks.dart';
|
||||
|
||||
@@ -1,19 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
|
||||
abstract class LunaModuleState extends ChangeNotifier {
|
||||
/// [ScrollController] to be used with all scrolling [ListView] (and similar scrolling widgets) within a module.
|
||||
final ScrollController scrollController = ScrollController();
|
||||
|
||||
/// Using [scrollController], scrolls the first scroll position back to the beginning of the list.
|
||||
Future<void> scrollBackList() => scrollController.lunaAnimateToStart();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
scrollController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// Reset the state back to the default
|
||||
void reset();
|
||||
}
|
||||
|
||||
4
lib/core/models/webhooks.dart
Normal file
4
lib/core/models/webhooks.dart
Normal file
@@ -0,0 +1,4 @@
|
||||
abstract class LunaWebhooks {
|
||||
/// Handle the incoming webhook for the module.
|
||||
Future<void> handle(Map<dynamic, dynamic> data);
|
||||
}
|
||||
@@ -14,6 +14,9 @@ class LunaFirebaseMessaging {
|
||||
/// Returns a [Stream] to handle any notifications that are tapped while the application is in the background (not terminated).
|
||||
Stream<RemoteMessage> get onMessageOpenedApp => FirebaseMessaging.onMessageOpenedApp;
|
||||
|
||||
/// Returns the Firebase Cloud Messaging device token for this device.
|
||||
Future<String> get token async => instance.getToken();
|
||||
|
||||
/// Request for permission to send a user notifications.
|
||||
///
|
||||
/// Returns true if permissions are allowed at either a full or provisional level.
|
||||
@@ -29,22 +32,65 @@ class LunaFirebaseMessaging {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the Firebase Cloud Messaging device token for this device.
|
||||
Future<String> get token async => instance.getToken();
|
||||
|
||||
/// Return a [StreamSubscription] that will show a notification banner on a newly received notification.
|
||||
///
|
||||
/// This listens on [FirebaseMessaging.onMessage], where the application must be open and in the foreground.
|
||||
StreamSubscription<RemoteMessage> showNotificationOnMessageListener() {
|
||||
StreamSubscription<RemoteMessage> onMessageListener() {
|
||||
return onMessage.listen((message) {
|
||||
if(message == null) return;
|
||||
LunaModule module = (message.data ?? {}).isNotEmpty ? LunaModule.DASHBOARD.fromKey(message.data['module']) : null;
|
||||
showLunaSnackBar(
|
||||
title: message.notification?.title ?? 'Unknown Content',
|
||||
message: message.notification?.body ?? LunaUI.TEXT_EMDASH,
|
||||
type: LunaSnackbarType.INFO,
|
||||
position: FlashPosition.top,
|
||||
duration: Duration(seconds: 6, milliseconds: 750),
|
||||
showButton: module != null,
|
||||
buttonOnPressed: () async => _handleWebhook(message),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns a [StreamSubscription] that will handle messages/notifications that are opened while LunaSea is running in the background.
|
||||
///
|
||||
/// This listens on [FirebaseMessaging.onMessageOpenedApp], where the application must be open but in the background.
|
||||
StreamSubscription<RemoteMessage> onMessageOpenedAppListener() => onMessageOpenedApp.listen(_handleWebhook);
|
||||
|
||||
/// Check to see if there was an initial [RemoteMessage] available to be accessed.
|
||||
///
|
||||
/// If so, handles the notification webhook.
|
||||
Future<void> checkAndHandleInitialMessage() async {
|
||||
RemoteMessage message = await FirebaseMessaging.instance.getInitialMessage();
|
||||
_handleWebhook(message);
|
||||
}
|
||||
|
||||
/// Shared webhook handler.
|
||||
Future<void> _handleWebhook(RemoteMessage message) async {
|
||||
if(message == null || (message.data ?? {}).isEmpty) return;
|
||||
// Extract module
|
||||
LunaModule module = LunaModule.DASHBOARD.fromKey(message.data['module']);
|
||||
if(module == null) {
|
||||
LunaLogger().warning(
|
||||
'LunaFirebaseMessaging',
|
||||
'_handleWebhook',
|
||||
'Unknown module found inside of RemoteMessage: ${message.data['module'] ?? 'null'}',
|
||||
);
|
||||
return;
|
||||
}
|
||||
String profile = message.data['profile'];
|
||||
if(profile?.isEmpty ?? true) {
|
||||
LunaLogger().warning(
|
||||
'LunaFirebaseMessaging',
|
||||
'_handleWebhook',
|
||||
'Invalid profile received in webhook: ${message.data['profile'] ?? 'null'}',
|
||||
);
|
||||
return;
|
||||
}
|
||||
bool result = await LunaProfile().safelyChangeProfiles(profile);
|
||||
if(result) {
|
||||
module?.handleWebhook(message.data);
|
||||
} else {
|
||||
showLunaErrorSnackBar(title: 'Unknown Profile', message: '"$profile" does not exist in ${Constants.APPLICATION_NAME}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ extension LunaModuleExtension on LunaModule {
|
||||
case LunaModule.SABNZBD: return SABnzbd.ROUTE_NAME;
|
||||
case LunaModule.SEARCH: return Search.ROUTE_NAME;
|
||||
case LunaModule.SETTINGS: return SettingsHomeRouter().route();
|
||||
case LunaModule.SONARR: return SonarrHomeRouter.ROUTE_NAME;
|
||||
case LunaModule.SONARR: return SonarrHomeRouter().route();
|
||||
case LunaModule.TAUTULLI: return TautulliHomeRouter.ROUTE_NAME;
|
||||
case LunaModule.WAKE_ON_LAN: return null;
|
||||
}
|
||||
@@ -104,7 +104,7 @@ extension LunaModuleExtension on LunaModule {
|
||||
case LunaModule.NZBGET: return CustomIcons.nzbget;
|
||||
case LunaModule.RADARR: return CustomIcons.radarr;
|
||||
case LunaModule.SABNZBD: return CustomIcons.sabnzbd;
|
||||
case LunaModule.SEARCH: return Icons.search;
|
||||
case LunaModule.SEARCH: return Icons.search_rounded;
|
||||
case LunaModule.SETTINGS: return CustomIcons.settings;
|
||||
case LunaModule.SONARR: return CustomIcons.television;
|
||||
case LunaModule.TAUTULLI: return CustomIcons.tautulli;
|
||||
@@ -209,6 +209,22 @@ extension LunaModuleExtension on LunaModule {
|
||||
throw Exception('Invalid LunaModule');
|
||||
}
|
||||
|
||||
Future<void> handleWebhook(Map<String, dynamic> data) async {
|
||||
switch(this) {
|
||||
case LunaModule.DASHBOARD: return;
|
||||
case LunaModule.LIDARR: return LidarrWebhooks().handle(data);
|
||||
case LunaModule.NZBGET: return;
|
||||
case LunaModule.RADARR: return RadarrWebhooks().handle(data);
|
||||
case LunaModule.SABNZBD: return;
|
||||
case LunaModule.SEARCH: return;
|
||||
case LunaModule.SETTINGS: return;
|
||||
case LunaModule.SONARR: return SonarrWebhooks().handle(data);
|
||||
case LunaModule.TAUTULLI: return;
|
||||
case LunaModule.WAKE_ON_LAN: return;
|
||||
}
|
||||
throw Exception('Invalid LunaModule');
|
||||
}
|
||||
|
||||
Future<void> launch() async {
|
||||
if(route != null) LunaState.navigatorKey.currentState.pushNamedAndRemoveUntil(route, (Route<dynamic> route) => false);
|
||||
}
|
||||
|
||||
@@ -4,23 +4,23 @@ import 'package:lunasea/modules/settings.dart';
|
||||
class LunaProfile {
|
||||
/// Returns a list of the profiles, sorted by their lowercase key/display name.
|
||||
List<String> profilesList() => Database.profilesBox.keys.map<String>((profile) => profile as String).toList()..sort((a,b) => a.toLowerCase().compareTo(b.toLowerCase()));
|
||||
|
||||
/// Safely change profiles.
|
||||
///
|
||||
/// Does this safely by:
|
||||
/// - Ensures that the passed in profile isn't already enabled
|
||||
/// - Ensures that the profile exists
|
||||
Future<bool> safelyChangeProfiles(String profile, { bool showSnackbar = true }) async {
|
||||
if(LunaDatabaseValue.ENABLED_PROFILE.data != profile) {
|
||||
if(Database.profilesBox.containsKey(profile)) {
|
||||
LunaDatabaseValue.ENABLED_PROFILE.put(profile);
|
||||
LunaState.reset(LunaState.navigatorKey.currentContext);
|
||||
if(showSnackbar) showLunaSuccessSnackBar(title: 'Changed Profile', message: profile);
|
||||
return true;
|
||||
} else {
|
||||
LunaLogger().warning('LunaProfile', 'changeProfile', 'Attempted to change profile to unknown profile: $profile');
|
||||
}
|
||||
if(LunaDatabaseValue.ENABLED_PROFILE.data == profile) return true;
|
||||
if(Database.profilesBox.containsKey(profile)) {
|
||||
LunaDatabaseValue.ENABLED_PROFILE.put(profile);
|
||||
LunaState.reset(LunaState.navigatorKey.currentContext);
|
||||
if(showSnackbar) showLunaSuccessSnackBar(title: 'Changed Profile', message: profile);
|
||||
return true;
|
||||
} else {
|
||||
LunaLogger().warning('LunaProfile', 'changeProfile', 'Attempted to change profile to unknown profile: $profile');
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Rename a profile.
|
||||
|
||||
@@ -54,7 +54,8 @@ class LunaBIOS extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _State extends State<LunaBIOS> {
|
||||
StreamSubscription _notificationListener;
|
||||
StreamSubscription _firebaseOnMessageListener;
|
||||
StreamSubscription _firebaseOnMessageOpenedAppListener;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -65,7 +66,8 @@ class _State extends State<LunaBIOS> {
|
||||
@override
|
||||
void dispose() {
|
||||
Future.wait([
|
||||
if(_notificationListener != null) _notificationListener.cancel(),
|
||||
if(_firebaseOnMessageListener != null) _firebaseOnMessageListener.cancel(),
|
||||
if(_firebaseOnMessageOpenedAppListener != null) _firebaseOnMessageOpenedAppListener.cancel(),
|
||||
Database().deinitialize(),
|
||||
LunaInAppPurchases().deinitialize(),
|
||||
]).then((_) => super.dispose());
|
||||
@@ -73,10 +75,16 @@ class _State extends State<LunaBIOS> {
|
||||
|
||||
/// Runs the first-step boot sequence that is required for widgets
|
||||
Future<void> _boot() async {
|
||||
// Initialize notifications: Request notification permission, add device token to Firebase (if logged in), add a notification listener for internal headsup
|
||||
// Initialize notifications
|
||||
// - Request notification permission
|
||||
// - Add device token to Firebase (if logged in)
|
||||
// - Check and handle initial message (if found)
|
||||
// - Add notification listeners for onMessage and onMessageOpenedApp
|
||||
await LunaFirebaseMessaging().requestNotificationPermissions();
|
||||
LunaFirebaseFirestore().addDeviceToken();
|
||||
_notificationListener = LunaFirebaseMessaging().showNotificationOnMessageListener();
|
||||
LunaFirebaseMessaging().checkAndHandleInitialMessage();
|
||||
_firebaseOnMessageListener = LunaFirebaseMessaging().onMessageListener();
|
||||
_firebaseOnMessageOpenedAppListener = LunaFirebaseMessaging().onMessageOpenedAppListener();
|
||||
// Initialize quick actions
|
||||
LunaQuickActions().initialize();
|
||||
// Check and show changelog
|
||||
|
||||
@@ -71,7 +71,7 @@ class CalendarSonarrData extends CalendarData {
|
||||
: '';
|
||||
}
|
||||
|
||||
Future<void> enterContent(BuildContext context) async => SonarrSeriesDetailsRouter.navigateTo(
|
||||
Future<void> enterContent(BuildContext context) async => SonarrSeriesDetailsRouter().navigateTo(
|
||||
context,
|
||||
seriesId: seriesID,
|
||||
);
|
||||
|
||||
@@ -118,8 +118,8 @@ class _State extends State<DashboardCalendarWidget> with TickerProviderStateMixi
|
||||
),
|
||||
centerHeaderTitle: true,
|
||||
formatButtonVisible: false,
|
||||
leftChevronIcon: Icon(Icons.arrow_back_ios),
|
||||
rightChevronIcon: Icon(Icons.arrow_forward_ios),
|
||||
leftChevronIcon: Icon(Icons.arrow_back_ios_rounded),
|
||||
rightChevronIcon: Icon(Icons.arrow_forward_ios_rounded),
|
||||
),
|
||||
initialCalendarFormat: (DashboardDatabaseValue.CALENDAR_STARTING_SIZE.data as CalendarStartingSize).data,
|
||||
availableCalendarFormats: const {
|
||||
|
||||
@@ -4,3 +4,4 @@ export 'core/database.dart';
|
||||
export 'core/dialogs.dart';
|
||||
export 'core/state.dart';
|
||||
export 'core/sorting.dart';
|
||||
export 'core/webhooks.dart';
|
||||
|
||||
66
lib/modules/lidarr/core/webhooks.dart
Normal file
66
lib/modules/lidarr/core/webhooks.dart
Normal file
@@ -0,0 +1,66 @@
|
||||
import 'package:lunasea/core.dart';
|
||||
//import 'package:lunasea/modules/Lidarr.dart';
|
||||
|
||||
class LidarrWebhooks extends LunaWebhooks {
|
||||
@override
|
||||
Future<void> handle(Map<dynamic, dynamic> data) async {
|
||||
_EventType event = _EventType.GRAB.fromKey(data['event']);
|
||||
if(event == null) LunaLogger().warning(
|
||||
'LidarrWebhooks',
|
||||
'handle',
|
||||
'Unknown event type: ${data['event'] ?? 'null'}',
|
||||
);
|
||||
event?.execute(data);
|
||||
}
|
||||
}
|
||||
|
||||
enum _EventType {
|
||||
DOWNLOAD,
|
||||
GRAB,
|
||||
RENAME,
|
||||
RETAG,
|
||||
TEST,
|
||||
}
|
||||
|
||||
extension _EventTypeExtension on _EventType {
|
||||
_EventType fromKey(String key) {
|
||||
switch(key) {
|
||||
case 'Download': return _EventType.DOWNLOAD;
|
||||
case 'Grab': return _EventType.GRAB;
|
||||
case 'Rename': return _EventType.RENAME;
|
||||
case 'Retag': return _EventType.RETAG;
|
||||
case 'Test': return _EventType.TEST;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> execute(Map<dynamic, dynamic> data) async {
|
||||
switch(this) {
|
||||
case _EventType.DOWNLOAD: return _downloadEvent(data);
|
||||
case _EventType.GRAB: return _grabEvent(data);
|
||||
case _EventType.RENAME: return _renameEvent(data);
|
||||
case _EventType.RETAG: return _retagEvent(data);
|
||||
case _EventType.TEST: return _testEvent(data);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _downloadEvent(Map<dynamic, dynamic> data) async {
|
||||
return LunaModule.LIDARR.launch();
|
||||
}
|
||||
|
||||
Future<void> _grabEvent(Map<dynamic, dynamic> data) async {
|
||||
return LunaModule.LIDARR.launch();
|
||||
}
|
||||
|
||||
Future<void> _renameEvent(Map<dynamic, dynamic> data) async {
|
||||
return LunaModule.LIDARR.launch();
|
||||
}
|
||||
|
||||
Future<void> _retagEvent(Map<dynamic, dynamic> data) async {
|
||||
return LunaModule.LIDARR.launch();
|
||||
}
|
||||
|
||||
Future<void> _testEvent(Map<dynamic, dynamic> data) async {
|
||||
return LunaModule.LIDARR.launch();
|
||||
}
|
||||
}
|
||||
@@ -180,7 +180,7 @@ class _State extends State<LidarrAddDetails> {
|
||||
return LSCardTile(
|
||||
title: LSTitle(text: 'Root Folder'),
|
||||
subtitle: LSSubtitle(text: _rootfolder?.path ?? 'Unknown Root Folder'),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
List _values = await LidarrDialogs.editRootFolder(context, _rootFolders);
|
||||
if(_values[0]) LidarrDatabaseValue.ADD_ROOT_FOLDER.put(_values[1]);
|
||||
@@ -195,7 +195,7 @@ class _State extends State<LidarrAddDetails> {
|
||||
return LSCardTile(
|
||||
title: LSTitle(text: 'Quality Profile'),
|
||||
subtitle: LSSubtitle(text: _profile?.name ?? 'Unknown Profile'),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
List _values = await LidarrDialogs.editQualityProfile(context, _qualityProfiles);
|
||||
if(_values[0]) LidarrDatabaseValue.ADD_QUALITY_PROFILE.put(_values[1]);
|
||||
@@ -210,7 +210,7 @@ class _State extends State<LidarrAddDetails> {
|
||||
return LSCardTile(
|
||||
title: LSTitle(text: 'Metadata Profile'),
|
||||
subtitle: LSSubtitle(text: _profile?.name ?? 'Unknown Profile'),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
List _values = await LidarrDialogs.editMetadataProfile(context, _metadataProfiles);
|
||||
if(_values[0]) LidarrDatabaseValue.ADD_METADATA_PROFILE.put(_values[1]);
|
||||
|
||||
@@ -73,7 +73,8 @@ class _State extends State<LidarrDetailsArtist> {
|
||||
? null
|
||||
: <Widget>[
|
||||
LidarrDetailsHideButton(),
|
||||
LidarrDetailsEditButton(
|
||||
LidarrDetailsEditButton(data: _arguments.data),
|
||||
LidarrDetailsSettingsButton(
|
||||
data: _arguments.data,
|
||||
remove: (bool withData) => _removeCallback(withData),
|
||||
),
|
||||
|
||||
@@ -129,19 +129,19 @@ class _State extends State<LidarrEditArtist> {
|
||||
LSCardTile(
|
||||
title: LSTitle(text: 'Artist Path'),
|
||||
subtitle: LSSubtitle(text: _path),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () => _changePath(),
|
||||
),
|
||||
LSCardTile(
|
||||
title: LSTitle(text: 'Quality Profile'),
|
||||
subtitle: LSSubtitle(text: _qualityProfile.name),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () => _changeProfile(),
|
||||
),
|
||||
LSCardTile(
|
||||
title: LSTitle(text: 'Metadata Profile'),
|
||||
subtitle: LSSubtitle(text: _metadataProfile.name),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () => _changeMetadata(),
|
||||
),
|
||||
LSButton(
|
||||
|
||||
@@ -6,6 +6,7 @@ export 'widgets/catalogue_sorting_button.dart';
|
||||
export 'widgets/catalogue_hide_button.dart';
|
||||
export 'widgets/details_edit_button.dart';
|
||||
export 'widgets/details_hide_button.dart';
|
||||
export 'widgets/details_settings_button.dart';
|
||||
export 'widgets/details_album_list.dart';
|
||||
export 'widgets/details_album_tile.dart';
|
||||
export 'widgets/details_album_track_tile.dart';
|
||||
|
||||
@@ -22,7 +22,7 @@ class LidarrAddSearchResultTile extends StatelessWidget {
|
||||
padContent: true,
|
||||
trailing: alreadyAdded
|
||||
? null
|
||||
: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
: LSIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: alreadyAdded
|
||||
? () => _showAlreadyAddedMessage(context)
|
||||
: () async => _enterDetails(context),
|
||||
|
||||
@@ -4,11 +4,9 @@ import 'package:lunasea/modules/lidarr.dart';
|
||||
|
||||
class LidarrDetailsEditButton extends StatefulWidget {
|
||||
final LidarrCatalogueData data;
|
||||
final Function(bool) remove;
|
||||
|
||||
LidarrDetailsEditButton({
|
||||
@required this.data,
|
||||
@required this.remove,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -20,20 +18,10 @@ class _State extends State<LidarrDetailsEditButton> {
|
||||
Widget build(BuildContext context) => Consumer<LidarrState>(
|
||||
builder: (context, model, widget) => LSIconButton(
|
||||
icon: Icons.edit,
|
||||
onPressed: () async => _handlePopup(context),
|
||||
onPressed: () async => _enterEditArtist(context),
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> _handlePopup(BuildContext context) async {
|
||||
List<dynamic> 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;
|
||||
case 'remove_artist': _removeArtist(context); break;
|
||||
default: LunaLogger().warning('LidarrDetailsEditButton', '_handlePopup', 'Invalid method passed through popup. (${values[1]})');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _enterEditArtist(BuildContext context) async {
|
||||
final dynamic result = await Navigator.of(context).pushNamed(
|
||||
LidarrEditArtist.ROUTE_NAME,
|
||||
@@ -46,30 +34,4 @@ class _State extends State<LidarrDetailsEditButton> {
|
||||
type: SNACKBAR_TYPE.success,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _refreshArtist(BuildContext context) async {
|
||||
final _api = LidarrAPI.from(Database.currentProfileObject);
|
||||
await _api.refreshArtist(widget.data.artistID)
|
||||
.then((_) => LSSnackBar(context: context, title: 'Refreshing...', message: widget.data.title))
|
||||
.catchError((_) => LSSnackBar(context: context, title: 'Failed to Refresh', message: LunaLogger.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure));
|
||||
}
|
||||
|
||||
Future<void> _removeArtist(BuildContext context) async {
|
||||
final _api = LidarrAPI.from(Database.currentProfileObject);
|
||||
List values = await LidarrDialogs.deleteArtist(context);
|
||||
if(values[0]) {
|
||||
if(values[1]) {
|
||||
values = await LunaDialogs().deleteCatalogueWithFiles(context, widget.data.title);
|
||||
if(values[0]) {
|
||||
await _api.removeArtist(widget.data.artistID, deleteFiles: true)
|
||||
.then((_) => widget.remove(true))
|
||||
.catchError((_) => LSSnackBar(context: context, title: 'Failed to Remove (With Data)', message: LunaLogger.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure));
|
||||
}
|
||||
} else {
|
||||
await _api.removeArtist(widget.data.artistID)
|
||||
.then((_) => widget.remove(false))
|
||||
.catchError((_) => LSSnackBar(context: context, title: 'Failed to Remove', message: LunaLogger.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
75
lib/modules/lidarr/widgets/details_settings_button.dart
Normal file
75
lib/modules/lidarr/widgets/details_settings_button.dart
Normal file
@@ -0,0 +1,75 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
import 'package:lunasea/modules/lidarr.dart';
|
||||
|
||||
class LidarrDetailsSettingsButton extends StatefulWidget {
|
||||
final LidarrCatalogueData data;
|
||||
final Function(bool) remove;
|
||||
|
||||
LidarrDetailsSettingsButton({
|
||||
@required this.data,
|
||||
@required this.remove,
|
||||
});
|
||||
|
||||
@override
|
||||
State<LidarrDetailsSettingsButton> createState() => _State();
|
||||
}
|
||||
|
||||
class _State extends State<LidarrDetailsSettingsButton> {
|
||||
@override
|
||||
Widget build(BuildContext context) => Consumer<LidarrState>(
|
||||
builder: (context, model, widget) => LSIconButton(
|
||||
icon: Icons.more_vert,
|
||||
onPressed: () async => _handlePopup(context),
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> _handlePopup(BuildContext context) async {
|
||||
List<dynamic> 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;
|
||||
case 'remove_artist': _removeArtist(context); break;
|
||||
default: LunaLogger().warning('LidarrDetailsSettingsButton', '_handlePopup', 'Invalid method passed through popup. (${values[1]})');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _enterEditArtist(BuildContext context) async {
|
||||
final dynamic result = await Navigator.of(context).pushNamed(
|
||||
LidarrEditArtist.ROUTE_NAME,
|
||||
arguments: LidarrEditArtistArguments(entry: widget.data),
|
||||
);
|
||||
if(result != null && result[0]) LSSnackBar(
|
||||
context: context,
|
||||
title: 'Updated',
|
||||
message: widget.data.title,
|
||||
type: SNACKBAR_TYPE.success,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _refreshArtist(BuildContext context) async {
|
||||
final _api = LidarrAPI.from(Database.currentProfileObject);
|
||||
await _api.refreshArtist(widget.data.artistID)
|
||||
.then((_) => LSSnackBar(context: context, title: 'Refreshing...', message: widget.data.title))
|
||||
.catchError((_) => LSSnackBar(context: context, title: 'Failed to Refresh', message: LunaLogger.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure));
|
||||
}
|
||||
|
||||
Future<void> _removeArtist(BuildContext context) async {
|
||||
final _api = LidarrAPI.from(Database.currentProfileObject);
|
||||
List values = await LidarrDialogs.deleteArtist(context);
|
||||
if(values[0]) {
|
||||
if(values[1]) {
|
||||
values = await LunaDialogs().deleteCatalogueWithFiles(context, widget.data.title);
|
||||
if(values[0]) {
|
||||
await _api.removeArtist(widget.data.artistID, deleteFiles: true)
|
||||
.then((_) => widget.remove(true))
|
||||
.catchError((_) => LSSnackBar(context: context, title: 'Failed to Remove (With Data)', message: LunaLogger.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure));
|
||||
}
|
||||
} else {
|
||||
await _api.removeArtist(widget.data.artistID)
|
||||
.then((_) => widget.remove(false))
|
||||
.catchError((_) => LSSnackBar(context: context, title: 'Failed to Remove', message: LunaLogger.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ class _State extends State<LidarrHistoryTile> {
|
||||
),
|
||||
),
|
||||
trailing: LSIconButton(
|
||||
icon: Icons.arrow_forward_ios,
|
||||
icon: Icons.arrow_forward_ios_rounded,
|
||||
),
|
||||
padContent: true,
|
||||
onTap: () async => _enterArtist(),
|
||||
|
||||
@@ -13,7 +13,7 @@ class NZBGetLogTile extends StatelessWidget {
|
||||
Widget build(BuildContext context) => LSCardTile(
|
||||
title: LSTitle(text: data.text),
|
||||
subtitle: LSSubtitle(text: data.timestamp),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => LunaDialogs().textPreview(context, 'Log Entry', data.text),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,3 +5,4 @@ export 'core/extensions.dart';
|
||||
export 'core/router.dart';
|
||||
export 'core/state.dart';
|
||||
export 'core/types.dart';
|
||||
export 'core/webhooks.dart';
|
||||
|
||||
78
lib/modules/radarr/core/webhooks.dart
Normal file
78
lib/modules/radarr/core/webhooks.dart
Normal file
@@ -0,0 +1,78 @@
|
||||
import 'package:lunasea/core.dart';
|
||||
import 'package:lunasea/modules/radarr.dart';
|
||||
|
||||
class RadarrWebhooks extends LunaWebhooks {
|
||||
@override
|
||||
Future<void> handle(Map<dynamic, dynamic> data) async {
|
||||
_EventType event = _EventType.GRAB.fromKey(data['event']);
|
||||
if(event == null) LunaLogger().warning(
|
||||
'RadarrWebhooks',
|
||||
'handle',
|
||||
'Unknown event type: ${data['event'] ?? 'null'}',
|
||||
);
|
||||
event?.execute(data);
|
||||
}
|
||||
}
|
||||
|
||||
enum _EventType {
|
||||
DOWNLOAD,
|
||||
GRAB,
|
||||
HEALTH,
|
||||
RENAME,
|
||||
TEST,
|
||||
}
|
||||
|
||||
extension _EventTypeExtension on _EventType {
|
||||
_EventType fromKey(String key) {
|
||||
switch(key) {
|
||||
case 'Download': return _EventType.DOWNLOAD;
|
||||
case 'Grab': return _EventType.GRAB;
|
||||
case 'Health': return _EventType.HEALTH;
|
||||
case 'Rename': return _EventType.RENAME;
|
||||
case 'Test': return _EventType.TEST;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> execute(Map<dynamic, dynamic> data) async {
|
||||
switch(this) {
|
||||
case _EventType.GRAB: return _grabEvent(data);
|
||||
case _EventType.DOWNLOAD: return _downloadEvent(data);
|
||||
case _EventType.HEALTH: return _healthEvent(data);
|
||||
case _EventType.RENAME: return _renameEvent(data);
|
||||
case _EventType.TEST: return _testEvent(data);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _downloadEvent(Map<dynamic, dynamic> data) async {
|
||||
int movieId = int.tryParse(data['id']);
|
||||
if(movieId != null) return RadarrMoviesDetailsRouter().navigateTo(
|
||||
LunaState.navigatorKey.currentContext,
|
||||
movieId: movieId,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _grabEvent(Map<dynamic, dynamic> data) async {
|
||||
int movieId = int.tryParse(data['id']);
|
||||
if(movieId != null) return RadarrMoviesDetailsRouter().navigateTo(
|
||||
LunaState.navigatorKey.currentContext,
|
||||
movieId: movieId,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _healthEvent(Map<dynamic, dynamic> data) async {
|
||||
return RadarrSystemStatusRouter().navigateTo(LunaState.navigatorKey.currentContext);
|
||||
}
|
||||
|
||||
Future<void> _renameEvent(Map<dynamic, dynamic> data) async {
|
||||
int movieId = int.tryParse(data['id']);
|
||||
if(movieId != null) return RadarrMoviesDetailsRouter().navigateTo(
|
||||
LunaState.navigatorKey.currentContext,
|
||||
movieId: movieId,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _testEvent(Map<dynamic, dynamic> data) async {
|
||||
return LunaModule.RADARR.launch();
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,6 @@ class _State extends State<RadarrAddMovieSearchPage> with AutomaticKeepAliveClie
|
||||
return LunaAppBar.empty(
|
||||
child: RadarrAddMovieSearchSearchBar(
|
||||
query: context.read<RadarrAddMovieState>().searchQuery,
|
||||
scrollController: context.watch<RadarrState>().scrollController,
|
||||
),
|
||||
height: 62.0,
|
||||
);
|
||||
|
||||
@@ -3,12 +3,10 @@ import 'package:lunasea/core.dart';
|
||||
import 'package:lunasea/modules/radarr.dart';
|
||||
|
||||
class RadarrAddMovieSearchSearchBar extends StatefulWidget implements PreferredSizeWidget {
|
||||
final ScrollController scrollController;
|
||||
final String query;
|
||||
|
||||
RadarrAddMovieSearchSearchBar({
|
||||
Key key,
|
||||
@required this.scrollController,
|
||||
@required this.query,
|
||||
}) : super(key: key);
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ class RadarrAddMovieDetailsMinimumAvailabilityTile extends StatelessWidget {
|
||||
selector: (_, state) => state.availability,
|
||||
builder: (context, availability, _) => LunaText.subtitle(text: availability?.readable ?? Constants.TEXT_EMDASH),
|
||||
),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
Tuple2<bool, RadarrAvailability> values = await RadarrDialogs().editMinimumAvailability(context);
|
||||
if(values.item1) context.read<RadarrAddMovieDetailsState>().availability = values.item2;
|
||||
|
||||
@@ -12,7 +12,7 @@ class RadarrAddMovieDetailsQualityProfileTile extends StatelessWidget {
|
||||
selector: (_, state) => state.qualityProfile,
|
||||
builder: (context, profile, _) => LunaText.subtitle(text: profile?.name ?? Constants.TEXT_EMDASH),
|
||||
),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
List<RadarrQualityProfile> qualityProfiles = await context.read<RadarrState>().qualityProfiles;
|
||||
Tuple2<bool, RadarrQualityProfile> values = await RadarrDialogs().editQualityProfile(context, qualityProfiles);
|
||||
|
||||
@@ -12,7 +12,7 @@ class RadarrAddMovieDetailsRootFolderTile extends StatelessWidget {
|
||||
selector: (_, state) => state.rootFolder,
|
||||
builder: (context, folder, _) => LunaText.subtitle(text: folder?.path ?? Constants.TEXT_EMDASH),
|
||||
),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
List<RadarrRootFolder> folders = await context.read<RadarrState>().rootFolders;
|
||||
Tuple2<bool, RadarrRootFolder> values = await RadarrDialogs().editRootFolder(context, folders);
|
||||
|
||||
@@ -11,7 +11,7 @@ class RadarrAddMovieDetailsTagsTile extends StatelessWidget {
|
||||
subtitle: LunaText.subtitle(text: context.watch<RadarrAddMovieDetailsState>().tags.length == 0
|
||||
? LunaUI.TEXT_EMDASH
|
||||
: context.watch<RadarrAddMovieDetailsState>().tags.map((e) => e.label).join(', ')),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => await RadarrDialogs().setAddTags(context),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ class RadarrMoviesEditPathTile extends StatelessWidget {
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Movie Path'),
|
||||
subtitle: LunaText.subtitle(text: context.watch<RadarrMoviesEditState>().path ?? Constants.TEXT_EMDASH),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
Tuple2<bool, String> _values = await LunaDialogs().editText(context, 'Movie Path', prefill: context.read<RadarrMoviesEditState>().path);
|
||||
if(_values.item1) context.read<RadarrMoviesEditState>().path = _values.item2;
|
||||
|
||||
@@ -16,7 +16,7 @@ class RadarrMoviesEditQualityProfileTile extends StatelessWidget {
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Quality Profile'),
|
||||
subtitle: LunaText.subtitle(text: context.watch<RadarrMoviesEditState>().qualityProfile?.name ?? Constants.TEXT_EMDASH),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
Tuple2<bool, RadarrQualityProfile> values = await RadarrDialogs().editQualityProfile(context, profiles);
|
||||
if(values.item1) context.read<RadarrMoviesEditState>().qualityProfile = values.item2;
|
||||
|
||||
@@ -9,7 +9,7 @@ class RadarrMoviesEditMinimumAvailabilityTile extends StatelessWidget {
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Minimum Availability'),
|
||||
subtitle: LunaText.subtitle(text: context.watch<RadarrMoviesEditState>().availability.readable),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
Tuple2<bool, RadarrAvailability> _values = await RadarrDialogs().editMinimumAvailability(context);
|
||||
if(_values.item1) context.read<RadarrMoviesEditState>().availability = _values.item2;
|
||||
|
||||
@@ -11,7 +11,7 @@ class RadarrMoviesEditTagsTile extends StatelessWidget {
|
||||
subtitle: LunaText.subtitle(text: context.watch<RadarrMoviesEditState>().tags.length == 0
|
||||
? 'Not Set'
|
||||
: context.watch<RadarrMoviesEditState>().tags.map((e) => e.label).join(', ')),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => await RadarrDialogs().setEditTags(context),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ class _State extends State<_RadarrMoviesDetailsRoute> with LunaLoadCallbackMixin
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
RadarrMovie movie;
|
||||
PageController _pageController;
|
||||
bool _loaded = false;
|
||||
|
||||
@override
|
||||
Future<void> loadCallback() async {
|
||||
@@ -62,7 +63,10 @@ class _State extends State<_RadarrMoviesDetailsRoute> with LunaLoadCallbackMixin
|
||||
(movie) => movie.id == widget.movieId,
|
||||
orElse: () => null,
|
||||
);
|
||||
if(mounted) setState(() => movie = _movie);
|
||||
if(mounted) setState(() {
|
||||
movie = _movie;
|
||||
_loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
List<RadarrTag> _findTags(List<int> tagIds, List<RadarrTag> tags) {
|
||||
@@ -82,13 +86,19 @@ class _State extends State<_RadarrMoviesDetailsRoute> with LunaLoadCallbackMixin
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: _appBar(),
|
||||
bottomNavigationBar: _bottomNavigationBar(),
|
||||
body: _body(),
|
||||
bottomNavigationBar: context.watch<RadarrState>().enabled ? _bottomNavigationBar() : null,
|
||||
body: context.watch<RadarrState>().enabled ? _body() : LunaMessage.moduleNotEnabled(context: context, module: 'Radarr'),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _appBar() {
|
||||
List<Widget> _actions = movie == null ? null : [RadarrAppBarMovieSettingsAction(movieId: widget.movieId)];
|
||||
List<Widget> _actions = movie == null ? null : [
|
||||
LunaIconButton(
|
||||
icon: Icons.edit,
|
||||
onPressed: () async => RadarrMoviesEditRouter().navigateTo(context, movieId: widget.movieId),
|
||||
),
|
||||
RadarrAppBarMovieSettingsAction(movieId: widget.movieId),
|
||||
];
|
||||
return LunaAppBar(
|
||||
pageController: _pageController,
|
||||
scrollControllers: RadarrMovieDetailsNavigationBar.scrollControllers,
|
||||
@@ -107,6 +117,10 @@ class _State extends State<_RadarrMoviesDetailsRoute> with LunaLoadCallbackMixin
|
||||
builder: (context, state, _) => FutureBuilder(
|
||||
future: Future.wait([state.qualityProfiles, state.tags]),
|
||||
builder: (context, AsyncSnapshot<List<Object>> snapshot) {
|
||||
if(_loaded && movie == null) return LunaMessage.goBack(
|
||||
text: 'Movie Not Found',
|
||||
context: context,
|
||||
);
|
||||
if(snapshot.hasError) {
|
||||
if(snapshot.connectionState != ConnectionState.waiting) LunaLogger().error(
|
||||
'Unable to pull Radarr movie details',
|
||||
|
||||
@@ -20,7 +20,7 @@ class RadarrAppBarMovieSettingsAction extends StatelessWidget {
|
||||
if(snapshot.hasData) {
|
||||
RadarrMovie movie = snapshot.data.firstWhere((element) => element.id == movieId, orElse: () => null);
|
||||
if(movie != null) return LunaIconButton(
|
||||
icon: Icons.edit,
|
||||
icon: Icons.more_vert,
|
||||
onPressed: () async {
|
||||
Tuple2<bool, RadarrMovieSettingsType> values = await RadarrDialogs().movieSettings(context, movie);
|
||||
if(values.item1) values.item2.execute(context, movie);
|
||||
|
||||
@@ -57,6 +57,19 @@ class _State extends State<RadarrMovieDetailsFilesFileBlock> {
|
||||
context: context,
|
||||
builder: (context) => LunaListViewModal(
|
||||
children: [
|
||||
LunaHeader(text: 'Video'),
|
||||
LunaTableCard(
|
||||
content: [
|
||||
LunaTableContent(title: 'bit depth', body: widget.movieFile.mediaInfo.videoBitDepth?.toString() ?? Constants.TEXT_EMDASH),
|
||||
LunaTableContent(title: 'bitrate', body: [
|
||||
widget.movieFile.mediaInfo.videoBitrate?.lunaBytesToString(bytes: false) ?? Constants.TEXT_EMDASH,
|
||||
if(widget.movieFile.mediaInfo.videoBitrate != null) '/s',
|
||||
].join()),
|
||||
LunaTableContent(title: 'codec', body: widget.movieFile.mediaInfo.videoCodec ?? Constants.TEXT_EMDASH),
|
||||
LunaTableContent(title: 'fps', body: widget.movieFile.mediaInfo.videoFps?.toString() ?? Constants.TEXT_EMDASH),
|
||||
LunaTableContent(title: 'resolution', body: widget.movieFile.mediaInfo.resolution ?? Constants.TEXT_EMDASH),
|
||||
],
|
||||
),
|
||||
LunaHeader(text: 'Audio'),
|
||||
LunaTableCard(
|
||||
content: [
|
||||
@@ -71,19 +84,6 @@ class _State extends State<RadarrMovieDetailsFilesFileBlock> {
|
||||
LunaTableContent(title: 'streams', body: widget.movieFile.mediaInfo.audioStreamCount?.toString() ?? Constants.TEXT_EMDASH),
|
||||
],
|
||||
),
|
||||
LunaHeader(text: 'Video'),
|
||||
LunaTableCard(
|
||||
content: [
|
||||
LunaTableContent(title: 'bit depth', body: widget.movieFile.mediaInfo.videoBitDepth?.toString() ?? Constants.TEXT_EMDASH),
|
||||
LunaTableContent(title: 'bitrate', body: [
|
||||
widget.movieFile.mediaInfo.videoBitrate?.lunaBytesToString(bytes: false) ?? Constants.TEXT_EMDASH,
|
||||
if(widget.movieFile.mediaInfo.videoBitrate != null) '/s',
|
||||
].join()),
|
||||
LunaTableContent(title: 'codec', body: widget.movieFile.mediaInfo.videoCodec ?? Constants.TEXT_EMDASH),
|
||||
LunaTableContent(title: 'fps', body: widget.movieFile.mediaInfo.videoFps?.toString() ?? Constants.TEXT_EMDASH),
|
||||
LunaTableContent(title: 'resolution', body: widget.movieFile.mediaInfo.resolution ?? Constants.TEXT_EMDASH),
|
||||
],
|
||||
),
|
||||
LunaHeader(text: 'Other'),
|
||||
LunaTableCard(
|
||||
content: [
|
||||
|
||||
@@ -56,7 +56,7 @@ class _State extends State<_RadarrReleasesRoute> with LunaScrollControllerMixin
|
||||
return LunaAppBar(
|
||||
title: 'Releases',
|
||||
scrollControllers: [scrollController],
|
||||
bottom: RadarrReleasesSearchBar(scrollController: context.read<RadarrState>().scrollController),
|
||||
bottom: RadarrReleasesSearchBar(scrollController: scrollController),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@ class _State extends State<_RadarrSystemStatusRoute> {
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: _appBar(),
|
||||
bottomNavigationBar: _bottomNavigationBar(),
|
||||
body: _body(),
|
||||
bottomNavigationBar: context.watch<RadarrState>().enabled ? _bottomNavigationBar() : null,
|
||||
body: context.watch<RadarrState>().enabled ? _body() : LunaMessage.moduleNotEnabled(context: context, module: 'Radarr'),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ class _State extends State<SABnzbdHistoryStages> {
|
||||
(index) => LSCardTile(
|
||||
title: LSTitle(text: _arguments.data.stageLog[index]['name']),
|
||||
subtitle: LSSubtitle(text: _arguments.data.stageLog[index]['actions'][0].replaceAll('<br/>', '.\n')),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
String _data = _arguments.data.stageLog[index]['actions'].join(',\n').replaceAll('<br/>', '.\n');
|
||||
LunaDialogs().textPreview(context, _arguments.data.stageLog[index]['name'], _data);
|
||||
|
||||
@@ -16,7 +16,7 @@ class SearchCategoryTile extends StatelessWidget {
|
||||
title: LSTitle(text: category.name),
|
||||
subtitle: LSSubtitle(text: category.subcategories.length == 0 ? 'No Subcategories Available': category.subcategoriesList),
|
||||
leading: LSIconButton(icon: category.icon, color: LunaColours.list(index)),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => _enterSubcategories(context),
|
||||
);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ class SearchIndexerTile extends StatelessWidget {
|
||||
Widget build(BuildContext context) => LSCardTile(
|
||||
title: LSTitle(text: indexer.displayName),
|
||||
subtitle: LSSubtitle(text: indexer.host),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
leading: LSIconButton(icon: Icons.rss_feed, color: LunaColours.list(index)),
|
||||
onTap: () async => _enterIndexer(context),
|
||||
);
|
||||
|
||||
@@ -21,7 +21,7 @@ class SearchSubcategoryTile extends StatelessWidget {
|
||||
Widget _card(BuildContext context) => LSCardTile(
|
||||
title: LSTitle(text: category?.subcategories[index ?? 0]?.name ?? 'Unknown'),
|
||||
subtitle: LSSubtitle(text: '${category?.name ?? 'Unknown'} > ${category?.subcategories[index ?? 0]?.name ?? 'Unknown'}'),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
leading: LSIconButton(icon: category?.icon, color: LunaColours.list(index+1)),
|
||||
onTap: () => _enterResults(
|
||||
context,
|
||||
@@ -34,7 +34,7 @@ class SearchSubcategoryTile extends StatelessWidget {
|
||||
title: LSTitle(text: 'All Subcategories'),
|
||||
subtitle: LSSubtitle(text: '${category?.name} > All'),
|
||||
leading: LSIconButton(icon: category?.icon, color: LunaColours.list(0)),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => _enterResults(
|
||||
context,
|
||||
category?.id ?? 0,
|
||||
|
||||
@@ -84,7 +84,7 @@ class _State extends State<_SettingsConfigurationAppearanceRoute> with LunaScrol
|
||||
? 'Disabled'
|
||||
: '${LunaDatabaseValue.THEME_IMAGE_BACKGROUND_OPACITY.data}%'
|
||||
),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
Tuple2<bool, int> result = await SettingsDialogs().changeBackgroundImageOpacity(context);
|
||||
if(result.item1) LunaDatabaseValue.THEME_IMAGE_BACKGROUND_OPACITY.put(result.item2);
|
||||
|
||||
@@ -62,7 +62,7 @@ class _State extends State<_SettingsConfigurationLidarrRoute> with LunaScrollCon
|
||||
? '1 Day'
|
||||
: '${DashboardDatabaseValue.CALENDAR_DAYS_PAST.data} Days',
|
||||
),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
List _values = await DashboardDialogs.setPastDays(context);
|
||||
if(_values[0]) DashboardDatabaseValue.CALENDAR_DAYS_PAST.put(_values[1]);
|
||||
@@ -80,7 +80,7 @@ class _State extends State<_SettingsConfigurationLidarrRoute> with LunaScrollCon
|
||||
? '1 Day'
|
||||
: '${DashboardDatabaseValue.CALENDAR_DAYS_FUTURE.data} Days',
|
||||
),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
List _values = await DashboardDialogs.setFutureDays(context);
|
||||
if(_values[0]) DashboardDatabaseValue.CALENDAR_DAYS_FUTURE.put(_values[1]);
|
||||
@@ -137,7 +137,7 @@ class _State extends State<_SettingsConfigurationLidarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Starting Type'),
|
||||
subtitle: LunaText.subtitle(text: (DashboardDatabaseValue.CALENDAR_STARTING_TYPE.data as CalendarStartingType).name),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
List _values = await SettingsDialogs.editCalendarStartingType(context);
|
||||
if(_values[0]) DashboardDatabaseValue.CALENDAR_STARTING_TYPE.put(_values[1]);
|
||||
@@ -152,7 +152,7 @@ class _State extends State<_SettingsConfigurationLidarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Starting Day'),
|
||||
subtitle: LunaText.subtitle(text: (DashboardDatabaseValue.CALENDAR_STARTING_DAY.data as CalendarStartingDay).name),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
List _values = await SettingsDialogs.editCalendarStartingDay(context);
|
||||
if(_values[0]) DashboardDatabaseValue.CALENDAR_STARTING_DAY.put(_values[1]);
|
||||
@@ -167,7 +167,7 @@ class _State extends State<_SettingsConfigurationLidarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Starting Size'),
|
||||
subtitle: LunaText.subtitle(text: (DashboardDatabaseValue.CALENDAR_STARTING_SIZE.data as CalendarStartingSize).name),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
List _values = await SettingsDialogs.editCalendarStartingSize(context);
|
||||
if(_values[0]) DashboardDatabaseValue.CALENDAR_STARTING_SIZE.put(_values[1]);
|
||||
|
||||
@@ -66,7 +66,7 @@ class _State extends State<_SettingsConfigurationHomeRoute> with LunaScrollContr
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Calendar Settings'),
|
||||
subtitle: LunaText.subtitle(text: 'Customize the Unified Calendar'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => SettingsConfigurationDashboardCalendarSettingsRouter().navigateTo(context),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ class _State extends State<_SettingsConfigurationLidarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Host'),
|
||||
subtitle: LunaText.subtitle(text: (host ?? '').isEmpty ? 'Not Set' : host),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SettingsDialogs.editHost(context, 'Lidarr Host', prefill: Database.currentProfileObject.lidarrHost ?? '');
|
||||
if(_values[0]) {
|
||||
@@ -74,7 +74,7 @@ class _State extends State<_SettingsConfigurationLidarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'API Key'),
|
||||
subtitle: LunaText.subtitle(text: (apiKey ?? '').isEmpty ? 'Not Set' : '••••••••••••'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
Tuple2<bool, String> _values = await LunaDialogs().editText(context, 'Lidarr API Key', prefill: Database.currentProfileObject.lidarrKey ?? '');
|
||||
if(_values.item1) {
|
||||
@@ -133,7 +133,7 @@ class _State extends State<_SettingsConfigurationLidarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Custom Headers'),
|
||||
subtitle: LunaText.subtitle(text: 'Add Custom Headers to Requests'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => SettingsConfigurationLidarrHeadersRouter().navigateTo(context),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ class _State extends State<_SettingsConfigurationLidarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Connection Details'),
|
||||
subtitle: LunaText.subtitle(text: 'Connection Details for Lidarr'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => SettingsConfigurationLidarrConnectionDetailsRouter().navigateTo(context),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ class _State extends State<_SettingsConfigurationNZBGetRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Host'),
|
||||
subtitle: LunaText.subtitle(text: (host ?? '').isEmpty ? 'Not Set' : host),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SettingsDialogs.editHost(context, 'NZBGet Host', prefill: Database.currentProfileObject.nzbgetHost ?? '');
|
||||
if(_values[0]) {
|
||||
@@ -75,7 +75,7 @@ class _State extends State<_SettingsConfigurationNZBGetRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Username'),
|
||||
subtitle: LunaText.subtitle(text: (username ?? '').isEmpty ? 'Not Set' : username),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
Tuple2<bool, String> _values = await LunaDialogs().editText(context, 'NZBGet Username', prefill: Database.currentProfileObject.nzbgetUser ?? '');
|
||||
if(_values.item1) {
|
||||
@@ -93,7 +93,7 @@ class _State extends State<_SettingsConfigurationNZBGetRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Password'),
|
||||
subtitle: LunaText.subtitle(text: (password ?? '').isEmpty ? 'Not Set' : '••••••••••••'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
Tuple2<bool, String> _values = await LunaDialogs().editText(
|
||||
context,
|
||||
@@ -153,7 +153,7 @@ class _State extends State<_SettingsConfigurationNZBGetRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Custom Headers'),
|
||||
subtitle: LunaText.subtitle(text: 'Add Custom Headers to Requests'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => SettingsConfigurationNZBGetHeadersRouter().navigateTo(context),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ class _State extends State<_SettingsConfigurationNZBGetRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Connection Details'),
|
||||
subtitle: LunaText.subtitle(text: 'Connection Details for NZBGet'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => SettingsConfigurationNZBGetConnectionDetailsRouter().navigateTo(context),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ class _State extends State<_SettingsConfigurationRadarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Host'),
|
||||
subtitle: LunaText.subtitle(text: (host ?? '').isEmpty ? 'Not Set' : host),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SettingsDialogs.editHost(context, 'Radarr Host', prefill: Database.currentProfileObject.radarrHost ?? '');
|
||||
if(_values[0]) {
|
||||
@@ -74,7 +74,7 @@ class _State extends State<_SettingsConfigurationRadarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'API Key'),
|
||||
subtitle: LunaText.subtitle(text: (apiKey ?? '').isEmpty ? 'Not Set' : '••••••••••••'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
Tuple2<bool, String> _values = await LunaDialogs().editText(context, 'Radarr API Key', prefill: Database.currentProfileObject.radarrKey ?? '');
|
||||
if(_values.item1) {
|
||||
@@ -133,7 +133,7 @@ class _State extends State<_SettingsConfigurationRadarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Custom Headers'),
|
||||
subtitle: LunaText.subtitle(text: 'Add Custom Headers to Requests'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => SettingsConfigurationRadarrHeadersRouter().navigateTo(context),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ class _State extends State<_SettingsConfigurationRadarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Movies Sort Category'),
|
||||
subtitle: LunaText.subtitle(text: (RadarrDatabaseValue.DEFAULT_SORTING_MOVIES.data as RadarrMoviesSorting).readable),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
List<String> titles = RadarrMoviesSorting.values.map<String>((e) => e.readable).toList();
|
||||
Tuple2<bool, int> values = await RadarrDialogs().setDefaultSortingOrFiltering(context, titles: titles);
|
||||
@@ -93,7 +93,7 @@ class _State extends State<_SettingsConfigurationRadarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Movies Filter Category'),
|
||||
subtitle: LunaText.subtitle(text: (RadarrDatabaseValue.DEFAULT_FILTERING_MOVIES.data as RadarrMoviesFilter).readable),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
List<String> titles = RadarrMoviesFilter.values.map<String>((e) => e.readable).toList();
|
||||
Tuple2<bool, int> values = await RadarrDialogs().setDefaultSortingOrFiltering(context, titles: titles);
|
||||
@@ -112,7 +112,7 @@ class _State extends State<_SettingsConfigurationRadarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Releases Sort Category'),
|
||||
subtitle: LunaText.subtitle(text: (RadarrDatabaseValue.DEFAULT_SORTING_RELEASES.data as RadarrReleasesSorting).readable),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
List<String> titles = RadarrReleasesSorting.values.map<String>((e) => e.readable).toList();
|
||||
Tuple2<bool, int> values = await RadarrDialogs().setDefaultSortingOrFiltering(context, titles: titles);
|
||||
@@ -146,7 +146,7 @@ class _State extends State<_SettingsConfigurationRadarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Releases Filter Category'),
|
||||
subtitle: LunaText.subtitle(text: (RadarrDatabaseValue.DEFAULT_FILTERING_RELEASES.data as RadarrReleasesFilter).readable),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
List<String> titles = RadarrReleasesFilter.values.map<String>((e) => e.readable).toList();
|
||||
Tuple2<bool, int> values = await RadarrDialogs().setDefaultSortingOrFiltering(context, titles: titles);
|
||||
|
||||
@@ -78,7 +78,7 @@ class _State extends State<_SettingsConfigurationRadarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Connection Details'),
|
||||
subtitle: LunaText.subtitle(text: 'Connection Details for Radarr'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => SettingsConfigurationRadarrConnectionDetailsRouter().navigateTo(context),
|
||||
);
|
||||
}
|
||||
@@ -88,7 +88,7 @@ class _State extends State<_SettingsConfigurationRadarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Default Pages'),
|
||||
subtitle: LunaText.subtitle(text: 'Set Default Landing Pages'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => SettingsConfigurationRadarrDefaultPagesRouter().navigateTo(context),
|
||||
);
|
||||
}
|
||||
@@ -98,7 +98,7 @@ class _State extends State<_SettingsConfigurationRadarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Default Sorting & Filtering'),
|
||||
subtitle: LunaText.subtitle(text: 'Set Default Sorting & Filtering Methods'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => SettingsConfigurationRadarrDefaultSortingRouter().navigateTo(context),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ class _State extends State<_SettingsConfigurationSABnzbdRoute> with LunaScrollCo
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Host'),
|
||||
subtitle: LunaText.subtitle(text: (host ?? '').isEmpty ? 'Not Set' : host),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SettingsDialogs.editHost(context, 'SABnzbd Host', prefill: Database.currentProfileObject.sabnzbdHost ?? '');
|
||||
if(_values[0]) {
|
||||
@@ -74,7 +74,7 @@ class _State extends State<_SettingsConfigurationSABnzbdRoute> with LunaScrollCo
|
||||
context: context,
|
||||
title: LunaText.title(text: 'API Key'),
|
||||
subtitle: LunaText.subtitle(text: (apiKey ?? '').isEmpty ? 'Not Set' : '••••••••••••'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
Tuple2<bool, String> _values = await LunaDialogs().editText(context, 'SABnzbd API Key', prefill: Database.currentProfileObject.sabnzbdKey ?? '');
|
||||
if(_values.item1) {
|
||||
@@ -133,7 +133,7 @@ class _State extends State<_SettingsConfigurationSABnzbdRoute> with LunaScrollCo
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Custom Headers'),
|
||||
subtitle: LunaText.subtitle(text: 'Add Custom Headers to Requests'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => SettingsConfigurationSABnzbdHeadersRouter().navigateTo(context),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ class _State extends State<_SettingsConfigurationSABnzbdRoute> with LunaScrollCo
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Connection Details'),
|
||||
subtitle: LunaText.subtitle(text: 'Connection Details for SABnzbd'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => SettingsConfigurationSABnzbdConnectionDetailsRouter().navigateTo(context),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ class _State extends State<_SettingsConfigurationSearchAddRoute> with LunaScroll
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Display Name'),
|
||||
subtitle: LunaText.subtitle(text: _indexer.displayName == null || _indexer.displayName.isEmpty ? 'Not Set' : _indexer.displayName),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
Tuple2<bool, String> values = await LunaDialogs().editText(context, 'Display Name', prefill: _indexer.displayName);
|
||||
if(values.item1 && mounted) setState(() => _indexer.displayName = values.item2);
|
||||
@@ -65,7 +65,7 @@ class _State extends State<_SettingsConfigurationSearchAddRoute> with LunaScroll
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Indexer API Host'),
|
||||
subtitle: LunaText.subtitle(text: _indexer.host == null || _indexer.host.isEmpty ? 'Not Set' : _indexer.host),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
Tuple2<bool, String> values = await LunaDialogs().editText(context, 'Indexer API Host', prefill: _indexer.host);
|
||||
if(values.item1 && mounted) setState(() => _indexer.host = values.item2);
|
||||
@@ -78,7 +78,7 @@ class _State extends State<_SettingsConfigurationSearchAddRoute> with LunaScroll
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Indexer API Key'),
|
||||
subtitle: LunaText.subtitle(text: _indexer.apiKey == null || _indexer.apiKey.isEmpty ? 'Not Set' : _indexer.apiKey),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
Tuple2<bool, String> values = await LunaDialogs().editText(context, 'Indexer API Key', prefill: _indexer.apiKey);
|
||||
if(values.item1 && mounted) setState(() => _indexer.apiKey = values.item2);
|
||||
@@ -91,7 +91,7 @@ class _State extends State<_SettingsConfigurationSearchAddRoute> with LunaScroll
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Custom Headers'),
|
||||
subtitle: LunaText.subtitle(text: 'Add Custom Headers to Requests'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => SettingsConfigurationSearchAddHeadersRouter().navigateTo(context, indexer: _indexer),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ class _State extends State<_SettingsConfigurationSearchEditRoute> with LunaScrol
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Display Name'),
|
||||
subtitle: LunaText.subtitle(text: _indexer.displayName == null || _indexer.displayName.isEmpty ? 'Not Set' : _indexer.displayName),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
Tuple2<bool, String> values = await LunaDialogs().editText(context, 'Display Name', prefill: _indexer.displayName);
|
||||
if(values.item1) _indexer.displayName = values.item2;
|
||||
@@ -96,7 +96,7 @@ class _State extends State<_SettingsConfigurationSearchEditRoute> with LunaScrol
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Indexer API Host'),
|
||||
subtitle: LunaText.subtitle(text: _indexer.host == null || _indexer.host.isEmpty ? 'Not Set' : _indexer.host),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
Tuple2<bool, String> values = await LunaDialogs().editText(context, 'Indexer API Host', prefill: _indexer.host);
|
||||
if(values.item1) _indexer.host = values.item2;
|
||||
@@ -110,7 +110,7 @@ class _State extends State<_SettingsConfigurationSearchEditRoute> with LunaScrol
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Indexer API Key'),
|
||||
subtitle: LunaText.subtitle(text: _indexer.apiKey == null || _indexer.apiKey.isEmpty ? 'Not Set' : _indexer.apiKey),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
Tuple2<bool, String> values = await LunaDialogs().editText(context, 'Indexer API Key', prefill: _indexer.apiKey);
|
||||
if(values.item1) _indexer.apiKey = values.item2;
|
||||
@@ -124,7 +124,7 @@ class _State extends State<_SettingsConfigurationSearchEditRoute> with LunaScrol
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Custom Headers'),
|
||||
subtitle: LunaText.subtitle(text: 'Add Custom Headers to Requests'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => SettingsConfigurationSearchEditHeadersRouter().navigateTo(context, indexerId: widget.indexerId),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ class _State extends State<_SettingsConfigurationSearchRoute> with LunaScrollCon
|
||||
Widget _indexerTile(IndexerHiveObject indexer, int index) => LSCardTile(
|
||||
title: LSTitle(text: indexer.displayName),
|
||||
subtitle: LSSubtitle(text: indexer.host),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => SettingsConfigurationSearchEditRouter().navigateTo(
|
||||
context,
|
||||
indexerId: index,
|
||||
|
||||
@@ -73,7 +73,7 @@ class _State extends State<_SettingsConfigurationSonarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Host'),
|
||||
subtitle: LunaText.subtitle(text: (host ?? '').isEmpty ? 'Not Set' : host),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SettingsDialogs.editHost(context, 'Sonarr Host', prefill: Database.currentProfileObject.sonarrHost ?? '');
|
||||
if(_values[0]) {
|
||||
@@ -91,7 +91,7 @@ class _State extends State<_SettingsConfigurationSonarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'API Key'),
|
||||
subtitle: LunaText.subtitle(text: (apiKey ?? '').isEmpty ? 'Not Set' : '••••••••••••'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
Tuple2<bool, String> _values = await LunaDialogs().editText(context, 'Sonarr API Key', prefill: Database.currentProfileObject.sonarrKey ?? '');
|
||||
if(_values.item1) {
|
||||
@@ -150,7 +150,7 @@ class _State extends State<_SettingsConfigurationSonarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Custom Headers'),
|
||||
subtitle: LunaText.subtitle(text: 'Add Custom Headers to Requests'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => SettingsConfigurationSonarrHeadersRouter().navigateTo(context),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ class _State extends State<_SettingsConfigurationSonarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Series Sort Category'),
|
||||
subtitle: LunaText.subtitle(text: (SonarrDatabaseValue.DEFAULT_SORTING_SERIES.data as SonarrSeriesSorting).readable),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
List<String> titles = SonarrSeriesSorting.values.map<String>((e) => e.readable).toList();
|
||||
List values = await SonarrDialogs.setDefaultSortingOrFiltering(context, titles: titles);
|
||||
@@ -91,7 +91,7 @@ class _State extends State<_SettingsConfigurationSonarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Releases Sort Category'),
|
||||
subtitle: LunaText.subtitle(text: (SonarrDatabaseValue.DEFAULT_SORTING_RELEASES.data as SonarrReleasesSorting).readable),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
List<String> titles = SonarrReleasesSorting.values.map<String>((e) => e.readable).toList();
|
||||
List values = await SonarrDialogs.setDefaultSortingOrFiltering(context, titles: titles);
|
||||
|
||||
@@ -77,7 +77,7 @@ class _State extends State<_SettingsConfigurationSonarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Connection Details'),
|
||||
subtitle: LunaText.subtitle(text: 'Connection Details for Sonarr'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => SettingsConfigurationSonarrConnectionDetailsRouter().navigateTo(context),
|
||||
);
|
||||
}
|
||||
@@ -87,7 +87,7 @@ class _State extends State<_SettingsConfigurationSonarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Default Pages'),
|
||||
subtitle: LunaText.subtitle(text: 'Set Default Landing Pages'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => SettingsConfigurationSonarrDefaultPagesRouter().navigateTo(context),
|
||||
);
|
||||
}
|
||||
@@ -97,7 +97,7 @@ class _State extends State<_SettingsConfigurationSonarrRoute> with LunaScrollCon
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Default Sorting & Filtering'),
|
||||
subtitle: LunaText.subtitle(text: 'Set Default Sorting & Filtering Methods'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => SettingsConfigurationSonarrDefaultSortingRouter().navigateTo(context),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ class _State extends State<_SettingsConfigurationTautulliRoute> with LunaScrollC
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Host'),
|
||||
subtitle: LunaText.subtitle(text: (host ?? '').isEmpty ? 'Not Set' : host),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SettingsDialogs.editHost(context, 'Tautulli Host', prefill: Database.currentProfileObject.tautulliHost ?? '');
|
||||
if(_values[0]) {
|
||||
@@ -74,7 +74,7 @@ class _State extends State<_SettingsConfigurationTautulliRoute> with LunaScrollC
|
||||
context: context,
|
||||
title: LunaText.title(text: 'API Key'),
|
||||
subtitle: LunaText.subtitle(text: (apiKey ?? '').isEmpty ? 'Not Set' : '••••••••••••'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
Tuple2<bool, String> _values = await LunaDialogs().editText(context, 'Tautulli API Key', prefill: Database.currentProfileObject.tautulliKey ?? '');
|
||||
if(_values.item1) {
|
||||
@@ -133,7 +133,7 @@ class _State extends State<_SettingsConfigurationTautulliRoute> with LunaScrollC
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Custom Headers'),
|
||||
subtitle: LunaText.subtitle(text: 'Add Custom Headers to Requests'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => SettingsConfigurationTautulliHeadersRouter().navigateTo(context),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ class _State extends State<_SettingsConfigurationTautulliRoute> with LunaScrollC
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Connection Details'),
|
||||
subtitle: LunaText.subtitle(text: 'Connection Details for Tautulli'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => SettingsConfigurationTautulliConnectionDetailsRouter().navigateTo(context),
|
||||
);
|
||||
}
|
||||
@@ -89,7 +89,7 @@ class _State extends State<_SettingsConfigurationTautulliRoute> with LunaScrollC
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Default Pages'),
|
||||
subtitle: LunaText.subtitle(text: 'Set Default Landing Pages'),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => SettingsConfigurationTautulliDefaultPagesRouter().navigateTo(context),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ class _State extends State<_SettingsConfigurationWakeOnLANRoute> with LunaScroll
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Broadcast Address'),
|
||||
subtitle: LunaText.subtitle(text: broadcastAddress == null || broadcastAddress == '' ? 'Not Set' : broadcastAddress),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SettingsDialogs.editBroadcastAddress(context, broadcastAddress ?? '');
|
||||
if(_values[0]) {
|
||||
@@ -90,7 +90,7 @@ class _State extends State<_SettingsConfigurationWakeOnLANRoute> with LunaScroll
|
||||
context: context,
|
||||
title: LunaText.title(text: 'Device MAC Address'),
|
||||
subtitle: LunaText.subtitle(text: macAddress == null || macAddress == '' ? 'Not Set' : macAddress),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LunaIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SettingsDialogs.editMACAddress(context, macAddress ?? '');
|
||||
if(_values[0]) {
|
||||
|
||||
@@ -5,3 +5,4 @@ export 'core/extensions.dart';
|
||||
export 'core/router.dart';
|
||||
export 'core/state.dart';
|
||||
export 'core/types.dart';
|
||||
export 'core/webhooks.dart';
|
||||
|
||||
@@ -5,16 +5,16 @@ import 'package:lunasea/modules/sonarr.dart';
|
||||
class SonarrRouter extends LunaModuleRouter {
|
||||
@override
|
||||
void defineAllRoutes(FluroRouter router) {
|
||||
SonarrHomeRouter.defineRoutes(router);
|
||||
SonarrHomeRouter().defineRoute(router);
|
||||
// Series
|
||||
SonarrSeriesAddRouter.defineRoutes(router);
|
||||
SonarrSeriesAddDetailsRouter.defineRoutes(router);
|
||||
SonarrSeriesEditRouter.defineRoutes(router);
|
||||
SonarrSeriesDetailsRouter.defineRoutes(router);
|
||||
SonarrSeriesSeasonDetailsRouter.defineRoutes(router);
|
||||
SonarrSeriesDetailsRouter().defineRoute(router);
|
||||
SonarrSeasonDetailsRouter().defineRoute(router);
|
||||
// Other
|
||||
SonarrQueueRouter.defineRoutes(router);
|
||||
SonarrQueueRouter().defineRoute(router);
|
||||
SonarrReleasesRouter.defineRoutes(router);
|
||||
SonarrTagsRouter.defineRoutes(router);
|
||||
SonarrTagsRouter().defineRoute(router);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,6 +280,17 @@ class SonarrState extends LunaModuleState {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> resetSingleSeries(int seriesId) async {
|
||||
assert(seriesId != null);
|
||||
if(_api != null) {
|
||||
SonarrSeries movie = await _api.series.getSeries(seriesId: seriesId);
|
||||
List<SonarrSeries> allSeries = await _series;
|
||||
int index = allSeries?.indexWhere((s) => s.id == seriesId) ?? -1;
|
||||
if(index >= 0) allSeries[index] = movie;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
///////////////
|
||||
/// MISSING ///
|
||||
///////////////
|
||||
|
||||
102
lib/modules/sonarr/core/webhooks.dart
Normal file
102
lib/modules/sonarr/core/webhooks.dart
Normal file
@@ -0,0 +1,102 @@
|
||||
import 'package:lunasea/core.dart';
|
||||
import 'package:lunasea/modules/sonarr.dart';
|
||||
|
||||
class SonarrWebhooks extends LunaWebhooks {
|
||||
@override
|
||||
Future<void> handle(Map<dynamic, dynamic> data) async {
|
||||
_EventType event = _EventType.GRAB.fromKey(data['event']);
|
||||
if(event == null) LunaLogger().warning(
|
||||
'SonarrWebhooks',
|
||||
'handle',
|
||||
'Unknown event type: ${data['event'] ?? 'null'}',
|
||||
);
|
||||
event?.execute(data);
|
||||
}
|
||||
}
|
||||
|
||||
enum _EventType {
|
||||
DOWNLOAD,
|
||||
EPISODE_FILE_DELETE,
|
||||
GRAB,
|
||||
HEALTH,
|
||||
RENAME,
|
||||
SERIES_DELETE,
|
||||
TEST,
|
||||
}
|
||||
|
||||
extension _EventTypeExtension on _EventType {
|
||||
_EventType fromKey(String key) {
|
||||
switch(key) {
|
||||
case 'Download': return _EventType.DOWNLOAD;
|
||||
case 'EpisodeFileDelete': return _EventType.EPISODE_FILE_DELETE;
|
||||
case 'Grab': return _EventType.GRAB;
|
||||
case 'Health': return _EventType.HEALTH;
|
||||
case 'Rename': return _EventType.RENAME;
|
||||
case 'SeriesDelete': return _EventType.SERIES_DELETE;
|
||||
case 'Test': return _EventType.TEST;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> execute(Map<dynamic, dynamic> data) async {
|
||||
switch(this) {
|
||||
case _EventType.DOWNLOAD: return _downloadEvent(data);
|
||||
case _EventType.EPISODE_FILE_DELETE: return _episodeFileDeleteEvent(data);
|
||||
case _EventType.GRAB: return _grabEvent(data);
|
||||
case _EventType.HEALTH: return _healthEvent(data);
|
||||
case _EventType.RENAME: return _renameEvent(data);
|
||||
case _EventType.SERIES_DELETE: return _seriesDeleteEvent(data);
|
||||
case _EventType.TEST: return _testEvent(data);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _downloadEvent(Map<dynamic, dynamic> data) async {
|
||||
int seriesId = int.tryParse(data['seriesId']);
|
||||
int seasonNumber = int.tryParse(data['seasonNumber']);
|
||||
if(seriesId != null && seasonNumber != null) SonarrSeasonDetailsRouter().navigateTo(
|
||||
LunaState.navigatorKey.currentContext,
|
||||
seriesId: seriesId,
|
||||
seasonNumber: seasonNumber,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _episodeFileDeleteEvent(Map<dynamic, dynamic> data) async {
|
||||
int seriesId = int.tryParse(data['seriesId']);
|
||||
int seasonNumber = int.tryParse(data['seasonNumber']);
|
||||
if(seriesId != null && seasonNumber != null) SonarrSeasonDetailsRouter().navigateTo(
|
||||
LunaState.navigatorKey.currentContext,
|
||||
seriesId: seriesId,
|
||||
seasonNumber: seasonNumber,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _grabEvent(Map<dynamic, dynamic> data) async {
|
||||
int seriesId = int.tryParse(data['seriesId']);
|
||||
int seasonNumber = int.tryParse(data['seasonNumber']);
|
||||
if(seriesId != null && seasonNumber != null) SonarrSeasonDetailsRouter().navigateTo(
|
||||
LunaState.navigatorKey.currentContext,
|
||||
seriesId: seriesId,
|
||||
seasonNumber: seasonNumber,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _healthEvent(Map<dynamic, dynamic> data) async {
|
||||
return LunaModule.SONARR.launch();
|
||||
}
|
||||
|
||||
Future<void> _renameEvent(Map<dynamic, dynamic> data) async {
|
||||
int seriesId = int.tryParse(data['seriesId']);
|
||||
if(seriesId != null) SonarrSeriesDetailsRouter().navigateTo(
|
||||
LunaState.navigatorKey.currentContext,
|
||||
seriesId: seriesId,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _seriesDeleteEvent(Map<dynamic, dynamic> data) async {
|
||||
return LunaModule.SONARR.launch();
|
||||
}
|
||||
|
||||
Future<void> _testEvent(Map<dynamic, dynamic> data) async {
|
||||
return LunaModule.SONARR.launch();
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,12 @@ export 'modules/history.dart';
|
||||
export 'modules/missing.dart';
|
||||
export 'modules/queue.dart';
|
||||
export 'modules/releases.dart';
|
||||
export 'modules/season_details.dart';
|
||||
export 'modules/series.dart';
|
||||
export 'modules/series_add.dart';
|
||||
export 'modules/series_add_details.dart';
|
||||
export 'modules/series_details.dart';
|
||||
export 'modules/series_edit.dart';
|
||||
export 'modules/series_season_details.dart';
|
||||
export 'modules/sonarr.dart';
|
||||
export 'modules/tags.dart';
|
||||
export 'modules/upcoming.dart';
|
||||
|
||||
@@ -102,7 +102,7 @@ class _State extends State<SonarrHistoryTile> {
|
||||
);
|
||||
|
||||
|
||||
Future<void> _onTap() async => SonarrSeriesDetailsRouter.navigateTo(
|
||||
Future<void> _onTap() async => SonarrSeriesDetailsRouter().navigateTo(
|
||||
context,
|
||||
seriesId: widget.record.seriesId,
|
||||
);
|
||||
|
||||
@@ -137,13 +137,13 @@ class _State extends State<SonarrMissingTile> {
|
||||
maxLines: 1,
|
||||
);
|
||||
|
||||
Future<void> _tileOnTap() async => SonarrSeriesSeasonDetailsRouter.navigateTo(
|
||||
Future<void> _tileOnTap() async => SonarrSeasonDetailsRouter().navigateTo(
|
||||
context,
|
||||
seriesId: widget.record.seriesId,
|
||||
seasonNumber: widget.record.seasonNumber,
|
||||
);
|
||||
|
||||
Future<void> _tileOnLongPress() async => SonarrSeriesDetailsRouter.navigateTo(
|
||||
Future<void> _tileOnLongPress() async => SonarrSeriesDetailsRouter().navigateTo(
|
||||
context,
|
||||
seriesId: widget.record.seriesId,
|
||||
);
|
||||
|
||||
@@ -4,23 +4,11 @@ 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';
|
||||
class SonarrQueueRouter extends LunaPageRouter {
|
||||
SonarrQueueRouter() : super('/sonarr/queue');
|
||||
|
||||
static Future<void> navigateTo(BuildContext context) async => LunaRouter.router.navigateTo(
|
||||
context,
|
||||
route(),
|
||||
);
|
||||
|
||||
static String route() => ROUTE_NAME;
|
||||
|
||||
static void defineRoutes(FluroRouter router) {
|
||||
router.define(
|
||||
ROUTE_NAME,
|
||||
handler: Handler(handlerFunc: (context, params) => _SonarrQueueRoute()),
|
||||
transitionType: LunaRouter.transitionType,
|
||||
);
|
||||
}
|
||||
@override
|
||||
void defineRoute(FluroRouter router) => super.noParameterRouteDefinition(router, _SonarrQueueRoute());
|
||||
}
|
||||
|
||||
class _SonarrQueueRoute extends StatefulWidget {
|
||||
|
||||
@@ -23,7 +23,7 @@ class SonarrReleasesRouter {
|
||||
}) {
|
||||
if(episodeId != null) return ROUTE_NAME+'/episode/$episodeId';
|
||||
if(seriesId != null && seasonNumber != null) return ROUTE_NAME+'/series/$seriesId/season/$seasonNumber';
|
||||
return SonarrHomeRouter.route();
|
||||
return SonarrHomeRouter().route();
|
||||
}
|
||||
|
||||
static void defineRoutes(FluroRouter router) {
|
||||
|
||||
2
lib/modules/sonarr/modules/season_details.dart
Normal file
2
lib/modules/sonarr/modules/season_details.dart
Normal file
@@ -0,0 +1,2 @@
|
||||
export 'season_details/route.dart';
|
||||
export 'season_details/widgets.dart';
|
||||
@@ -4,41 +4,38 @@ import 'package:flutter/scheduler.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
import 'package:lunasea/modules/sonarr.dart';
|
||||
|
||||
class SonarrSeriesSeasonDetailsRouter {
|
||||
static const String ROUTE_NAME = '/sonarr/series/details/:seriesid/season/:seasonnumber';
|
||||
class SonarrSeasonDetailsRouter extends LunaPageRouter {
|
||||
SonarrSeasonDetailsRouter() : super('/sonarr/series/details/:seriesid/season/:seasonnumber');
|
||||
|
||||
static Future<void> navigateTo(BuildContext context, {
|
||||
@override
|
||||
Future<void> navigateTo(BuildContext context, {
|
||||
@required int seriesId,
|
||||
@required int seasonNumber,
|
||||
}) async => LunaRouter.router.navigateTo(
|
||||
context,
|
||||
route(seriesId: seriesId, seasonNumber: seasonNumber),
|
||||
}) async => LunaRouter.router.navigateTo(context, route(seriesId: seriesId, seasonNumber: seasonNumber));
|
||||
|
||||
@override
|
||||
String route({
|
||||
@required int seriesId,
|
||||
@required int seasonNumber,
|
||||
}) => fullRoute.replaceFirst(':seriesid', seriesId.toString()).replaceFirst(':seasonnumber', seasonNumber.toString());
|
||||
|
||||
@override
|
||||
void defineRoute(FluroRouter router) => router.define(
|
||||
fullRoute,
|
||||
handler: Handler(handlerFunc: (context, params) {
|
||||
int seriesId = params['seriesid'] == null || params['seriesid'].length == 0 ? -1 : (int.tryParse(params['seriesid'][0]) ?? -1);
|
||||
int seasonNumber = params['seasonnumber'] == null || params['seasonnumber'].length == 0 ? -1 : (int.tryParse(params['seasonnumber'][0]) ?? -1);
|
||||
return _SonarrSeasonDetailsRoute(seriesId: seriesId, seasonNumber: seasonNumber);
|
||||
}),
|
||||
transitionType: LunaRouter.transitionType,
|
||||
);
|
||||
|
||||
static String route({
|
||||
@required int seriesId,
|
||||
@required int seasonNumber,
|
||||
}) => ROUTE_NAME
|
||||
.replaceFirst(':seriesid', seriesId.toString())
|
||||
.replaceFirst(':seasonnumber', seasonNumber.toString());
|
||||
|
||||
static void defineRoutes(FluroRouter router) {
|
||||
router.define(
|
||||
ROUTE_NAME,
|
||||
handler: Handler(handlerFunc: (context, params) => _SonarrSeriesSeasonDetailsRoute(
|
||||
seriesId: int.tryParse(params['seriesid'][0]) ?? -1,
|
||||
seasonNumber: int.tryParse(params['seasonnumber'][0]) ?? -1,
|
||||
)),
|
||||
transitionType: LunaRouter.transitionType,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SonarrSeriesSeasonDetailsRoute extends StatefulWidget {
|
||||
class _SonarrSeasonDetailsRoute extends StatefulWidget {
|
||||
final int seriesId;
|
||||
final int seasonNumber;
|
||||
|
||||
_SonarrSeriesSeasonDetailsRoute({
|
||||
_SonarrSeasonDetailsRoute({
|
||||
Key key,
|
||||
@required this.seriesId,
|
||||
@required this.seasonNumber,
|
||||
@@ -48,7 +45,7 @@ class _SonarrSeriesSeasonDetailsRoute extends StatefulWidget {
|
||||
State<StatefulWidget> createState() => _State();
|
||||
}
|
||||
|
||||
class _State extends State<_SonarrSeriesSeasonDetailsRoute> {
|
||||
class _State extends State<_SonarrSeasonDetailsRoute> {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
final GlobalKey<RefreshIndicatorState> _refreshKey = GlobalKey<RefreshIndicatorState>();
|
||||
|
||||
@@ -62,19 +59,24 @@ class _State extends State<_SonarrSeriesSeasonDetailsRoute> {
|
||||
}
|
||||
|
||||
Future<void> _refresh() async {
|
||||
if(context.read<SonarrState>().api != null)
|
||||
context.read<SonarrState>().fetchEpisodes(widget.seriesId);
|
||||
if(context.read<SonarrState>().episodes[widget.seriesId] != null)
|
||||
await context.read<SonarrState>().episodes[widget.seriesId];
|
||||
if(widget.seriesId > 0) {
|
||||
if(context.read<SonarrState>().api != null)
|
||||
context.read<SonarrState>().fetchEpisodes(widget.seriesId);
|
||||
if(context.read<SonarrState>().episodes[widget.seriesId] != null)
|
||||
await context.read<SonarrState>().episodes[widget.seriesId];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: _appBar,
|
||||
body: _body,
|
||||
floatingActionButton: _floatingActionButton,
|
||||
);
|
||||
Widget build(BuildContext context) {
|
||||
if(widget.seriesId <= 0) return LunaInvalidRoute(title: 'Season Details', message: 'Series Not Found');
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: _appBar,
|
||||
body: context.watch<SonarrState>().enabled ? _body : LunaMessage.moduleNotEnabled(context: context, module: 'Sonarr'),
|
||||
floatingActionButton: _floatingActionButton,
|
||||
);
|
||||
}
|
||||
|
||||
Widget get _appBar => LunaAppBar(title: 'Season Details');
|
||||
|
||||
@@ -96,7 +98,7 @@ class _State extends State<_SonarrSeriesSeasonDetailsRoute> {
|
||||
builder: (context, AsyncSnapshot<List<SonarrEpisode>> snapshot) {
|
||||
if(snapshot.hasError) return LSErrorMessage(onTapHandler: () => _refresh());
|
||||
if(snapshot.hasData) {
|
||||
if(widget.seasonNumber == -1) return SonarrSeriesSeasonDetailsAllSeasons(
|
||||
if(widget.seasonNumber == -1) return SonarrSeasonDetailsAllSeasons(
|
||||
episodes: snapshot.data,
|
||||
seriesId: widget.seriesId,
|
||||
);
|
||||
@@ -105,7 +107,7 @@ class _State extends State<_SonarrSeriesSeasonDetailsRoute> {
|
||||
).toList();
|
||||
if(_episodes != null && _episodes.length > 0) {
|
||||
_episodes.sort((a,b) => (b.episodeNumber ?? 0).compareTo(a.episodeNumber ?? 0));
|
||||
return SonarrSeriesSeasonDetailsSeason(
|
||||
return SonarrSeasonDetailsSeason(
|
||||
episodes: _episodes,
|
||||
seriesId: widget.seriesId,
|
||||
seasonNumber: widget.seasonNumber,
|
||||
@@ -2,11 +2,11 @@ import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
import 'package:lunasea/modules/sonarr.dart';
|
||||
|
||||
class SonarrSeriesSeasonDetailsAllSeasons extends StatelessWidget {
|
||||
class SonarrSeasonDetailsAllSeasons extends StatelessWidget {
|
||||
final int seriesId;
|
||||
final List<SonarrEpisode> episodes;
|
||||
|
||||
SonarrSeriesSeasonDetailsAllSeasons({
|
||||
SonarrSeasonDetailsAllSeasons({
|
||||
Key key,
|
||||
@required this.seriesId,
|
||||
@required this.episodes,
|
||||
@@ -38,14 +38,14 @@ class SonarrSeriesSeasonDetailsAllSeasons extends StatelessWidget {
|
||||
}
|
||||
|
||||
List<Widget> _season(List<SonarrEpisode> episodes, int seasonNumber) => [
|
||||
SonarrSeriesSeasonDetailsSeasonHeader(
|
||||
SonarrSeasonDetailsSeasonHeader(
|
||||
seriesId: seriesId,
|
||||
seasonNumber: seasonNumber,
|
||||
episodes: episodes,
|
||||
),
|
||||
...List.generate(
|
||||
episodes.length,
|
||||
(index) => SonarrSeriesSeasonDetailsEpisodeTile(episode: episodes[index]),
|
||||
(index) => SonarrSeasonDetailsEpisodeTile(episode: episodes[index]),
|
||||
),
|
||||
];
|
||||
}
|
||||
@@ -2,12 +2,12 @@ import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
import 'package:lunasea/modules/sonarr.dart';
|
||||
|
||||
class SonarrSeriesSeasonDetailsSeason extends StatelessWidget {
|
||||
class SonarrSeasonDetailsSeason extends StatelessWidget {
|
||||
final int seasonNumber;
|
||||
final int seriesId;
|
||||
final List<SonarrEpisode> episodes;
|
||||
|
||||
SonarrSeriesSeasonDetailsSeason({
|
||||
SonarrSeasonDetailsSeason({
|
||||
Key key,
|
||||
@required this.seasonNumber,
|
||||
@required this.seriesId,
|
||||
@@ -17,14 +17,14 @@ class SonarrSeriesSeasonDetailsSeason extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) => LSListView(
|
||||
children: [
|
||||
SonarrSeriesSeasonDetailsSeasonHeader(
|
||||
SonarrSeasonDetailsSeasonHeader(
|
||||
seriesId: seriesId,
|
||||
seasonNumber: seasonNumber,
|
||||
episodes: episodes,
|
||||
),
|
||||
...List.generate(
|
||||
episodes.length,
|
||||
(index) => SonarrSeriesSeasonDetailsEpisodeTile(episode: episodes[index]),
|
||||
(index) => SonarrSeasonDetailsEpisodeTile(episode: episodes[index]),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -2,12 +2,12 @@ import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
import 'package:lunasea/modules/sonarr.dart';
|
||||
|
||||
class SonarrSeriesSeasonDetailsSeasonHeader extends StatelessWidget {
|
||||
class SonarrSeasonDetailsSeasonHeader extends StatelessWidget {
|
||||
final int seasonNumber;
|
||||
final int seriesId;
|
||||
final List<SonarrEpisode> episodes;
|
||||
|
||||
SonarrSeriesSeasonDetailsSeasonHeader({
|
||||
SonarrSeasonDetailsSeasonHeader({
|
||||
Key key,
|
||||
@required this.seriesId,
|
||||
@required this.seasonNumber,
|
||||
@@ -4,19 +4,19 @@ import 'package:intl/intl.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
import 'package:lunasea/modules/sonarr.dart';
|
||||
|
||||
class SonarrSeriesSeasonDetailsEpisodeTile extends StatefulWidget {
|
||||
class SonarrSeasonDetailsEpisodeTile extends StatefulWidget {
|
||||
final SonarrEpisode episode;
|
||||
|
||||
SonarrSeriesSeasonDetailsEpisodeTile({
|
||||
SonarrSeasonDetailsEpisodeTile({
|
||||
Key key,
|
||||
@required this.episode,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<SonarrSeriesSeasonDetailsEpisodeTile> createState() => _State();
|
||||
State<SonarrSeasonDetailsEpisodeTile> createState() => _State();
|
||||
}
|
||||
|
||||
class _State extends State<SonarrSeriesSeasonDetailsEpisodeTile> {
|
||||
class _State extends State<SonarrSeasonDetailsEpisodeTile> {
|
||||
final ExpandableController _expandableController = ExpandableController();
|
||||
|
||||
@override
|
||||
@@ -218,5 +218,5 @@ class _State extends State<SonarrSeriesTile> {
|
||||
maxLines: 1,
|
||||
);
|
||||
|
||||
Future<void> _tileOnTap() async => SonarrSeriesDetailsRouter.navigateTo(context, seriesId: widget.series.id);
|
||||
Future<void> _tileOnTap() async => SonarrSeriesDetailsRouter().navigateTo(context, seriesId: widget.series.id);
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ class _State extends State<SonarrSeriesAddSearchResultTile> {
|
||||
LunaDialogs().textPreview(context, widget.series.title, widget.series.overview ?? 'No summary is available.');
|
||||
} else if(widget.exists) {
|
||||
Provider.of<SonarrState>(context, listen: false).enableVersion3
|
||||
? SonarrSeriesDetailsRouter.navigateTo(context, seriesId: widget.series.id ?? -1)
|
||||
? SonarrSeriesDetailsRouter().navigateTo(context, seriesId: widget.series.id ?? -1)
|
||||
: LSSnackBar(
|
||||
context: context,
|
||||
title: 'Series Already Exists',
|
||||
|
||||
@@ -113,7 +113,7 @@ class _State extends State<SonarrSeriesAddDetailsAddSeriesButton> {
|
||||
message: context.read<SonarrSeriesAddDetailsState>().series.title,
|
||||
type: SNACKBAR_TYPE.success,
|
||||
);
|
||||
Navigator.of(context).popAndPushNamed(SonarrSeriesDetailsRouter.route(seriesId: addedSeries.id));
|
||||
Navigator.of(context).popAndPushNamed(SonarrSeriesDetailsRouter().route(seriesId: addedSeries.id));
|
||||
})
|
||||
.catchError((error, stack) {
|
||||
LunaLogger().error('Failed to add series: ${context.read<SonarrSeriesAddDetailsState>().series.tvdbId}', error, stack);
|
||||
|
||||
@@ -7,7 +7,7 @@ class SonarrSeriesAddDetailsLanguageProfileTile extends StatelessWidget {
|
||||
Widget build(BuildContext context) => LSCardTile(
|
||||
title: LSTitle(text: 'Language Profile'),
|
||||
subtitle: LSSubtitle(text: context.watch<SonarrSeriesAddDetailsState>().languageProfile?.name ?? Constants.TEXT_EMDASH),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => _onTap(context),
|
||||
);
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ class SonarrSeriesAddDetailsMonitorStatusTile extends StatelessWidget {
|
||||
text: (SonarrDatabaseValue.ADD_SERIES_DEFAULT_MONITOR_STATUS.data as SonarrMonitorStatus).name,
|
||||
),
|
||||
),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => _onTap(context),
|
||||
);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ class SonarrSeriesAddDetailsQualityProfileTile extends StatelessWidget {
|
||||
Widget build(BuildContext context) => LSCardTile(
|
||||
title: LSTitle(text: 'Quality Profile'),
|
||||
subtitle: LSSubtitle(text: context.watch<SonarrSeriesAddDetailsState>().qualityProfile?.name ?? Constants.TEXT_EMDASH),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => _onTap(context),
|
||||
);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ class SonarrSeriesAddDetailsRootFolderTile extends StatelessWidget {
|
||||
Widget build(BuildContext context) => LSCardTile(
|
||||
title: LSTitle(text: 'Root Folder'),
|
||||
subtitle: LSSubtitle(text: context.watch<SonarrSeriesAddDetailsState>().rootFolder?.path ?? Constants.TEXT_EMDASH),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => _onTap(context),
|
||||
);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ class SonarrSeriesAddDetailsSeriesTypeTile extends StatelessWidget {
|
||||
Widget build(BuildContext context) => LSCardTile(
|
||||
title: LSTitle(text: 'Series Type'),
|
||||
subtitle: LSSubtitle(text: context.watch<SonarrSeriesAddDetailsState>().seriesType?.value?.lunaCapitalizeFirstLetters() ?? Constants.TEXT_EMDASH),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => _onTap(context),
|
||||
);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ class SonarrSeriesAddDetailsTagsTile extends StatelessWidget {
|
||||
subtitle: LSSubtitle(text: context.watch<SonarrSeriesAddDetailsState>().tags.length == 0
|
||||
? 'Not Set'
|
||||
: context.watch<SonarrSeriesAddDetailsState>().tags.map((e) => e.label).join(', ')),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios_rounded),
|
||||
onTap: () async => _onTap(context),
|
||||
);
|
||||
|
||||
|
||||
@@ -1,32 +1,27 @@
|
||||
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';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class SonarrSeriesDetailsRouter {
|
||||
static const String ROUTE_NAME = '/sonarr/series/details/:seriesid';
|
||||
class SonarrSeriesDetailsRouter extends LunaPageRouter {
|
||||
SonarrSeriesDetailsRouter() : super('/sonarr/series/details/:seriesid');
|
||||
|
||||
static Future<void> navigateTo(BuildContext context, {
|
||||
@required int seriesId,
|
||||
}) async => LunaRouter.router.navigateTo(
|
||||
context,
|
||||
route(seriesId: seriesId),
|
||||
@override
|
||||
Future<void> navigateTo(BuildContext context, { @required int seriesId }) async => LunaRouter.router.navigateTo(context, route(seriesId: seriesId));
|
||||
|
||||
@override
|
||||
String route({ @required int seriesId }) => fullRoute.replaceFirst(':seriesid', seriesId.toString());
|
||||
|
||||
@override
|
||||
void defineRoute(FluroRouter router) => router.define(
|
||||
fullRoute,
|
||||
handler: Handler(handlerFunc: (context, params) {
|
||||
int seriesId = params['seriesid'] == null || params['seriesid'].length == 0 ? -1 : (int.tryParse(params['seriesid'][0]) ?? -1);
|
||||
return _SonarrSeriesDetailsRoute(seriesId: seriesId);
|
||||
}),
|
||||
transitionType: LunaRouter.transitionType,
|
||||
);
|
||||
|
||||
static String route({ @required int seriesId }) => ROUTE_NAME
|
||||
.replaceFirst(':seriesid', seriesId?.toString() ?? '-1');
|
||||
|
||||
static void defineRoutes(FluroRouter router) {
|
||||
router.define(
|
||||
ROUTE_NAME,
|
||||
handler: Handler(handlerFunc: (context, params) => _SonarrSeriesDetailsRoute(
|
||||
seriesId: int.tryParse(params['seriesid'][0]) ?? -1,
|
||||
)),
|
||||
transitionType: LunaRouter.transitionType,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SonarrSeriesDetailsRoute extends StatefulWidget {
|
||||
@@ -41,32 +36,36 @@ class _SonarrSeriesDetailsRoute extends StatefulWidget {
|
||||
State<StatefulWidget> createState() => _State();
|
||||
}
|
||||
|
||||
class _State extends State<_SonarrSeriesDetailsRoute> {
|
||||
class _State extends State<_SonarrSeriesDetailsRoute> with LunaLoadCallbackMixin {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
PageController _pageController;
|
||||
SonarrSeries series;
|
||||
bool _loaded = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_pageController = PageController(initialPage: SonarrDatabaseValue.NAVIGATION_INDEX_SERIES_DETAILS.data);
|
||||
SchedulerBinding.instance.scheduleFrameCallback((_) => _refresh());
|
||||
}
|
||||
|
||||
Future<void> _refresh() async {
|
||||
SonarrState _state = Provider.of<SonarrState>(context, listen: false);
|
||||
SonarrSeries _series = await _state.api.series.getSeries(seriesId: widget.seriesId);
|
||||
List<SonarrSeries> allSeries = await _state.series;
|
||||
int _index = allSeries?.indexWhere((element) => element.id == widget.seriesId) ?? -1;
|
||||
if(_index >= 0) allSeries[_index] = _series;
|
||||
_state.notify();
|
||||
if(mounted) setState(() {});
|
||||
@override
|
||||
Future<void> loadCallback() async {
|
||||
if(widget.seriesId > 0) {
|
||||
_findSeries(await context.read<SonarrState>().series);
|
||||
await context.read<SonarrState>().resetSingleSeries(widget.seriesId);
|
||||
_findSeries(await context.read<SonarrState>().series);
|
||||
}
|
||||
}
|
||||
|
||||
SonarrSeries _findSeries(List<SonarrSeries> series) {
|
||||
return series.firstWhere(
|
||||
void _findSeries(List<SonarrSeries> seriesList) {
|
||||
SonarrSeries _series = seriesList.firstWhere(
|
||||
(series) => series.id == widget.seriesId,
|
||||
orElse: () => null,
|
||||
);
|
||||
if(mounted) setState(() {
|
||||
series = _series;
|
||||
_loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
List<SonarrTag> _findTags(List<int> tagIds, List<SonarrTag> tags) {
|
||||
@@ -89,16 +88,20 @@ class _State extends State<_SonarrSeriesDetailsRoute> {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: _appBar,
|
||||
bottomNavigationBar: _bottomNavigationBar,
|
||||
body: _body,
|
||||
);
|
||||
Widget build(BuildContext context) {
|
||||
if(widget.seriesId <= 0) return LunaInvalidRoute(title: 'Series Details', message: 'Series Not Found');
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: _appBar,
|
||||
bottomNavigationBar: context.watch<SonarrState>().enabled ? _bottomNavigationBar : null,
|
||||
body: context.watch<SonarrState>().enabled ? _body : LunaMessage.moduleNotEnabled(context: context, module: 'Sonarr'),
|
||||
);
|
||||
}
|
||||
|
||||
Widget get _appBar => LunaAppBar(
|
||||
title: 'Series Details',
|
||||
actions: [
|
||||
SonarrAppBarSeriesEditAction(seriesId: widget.seriesId),
|
||||
SonarrAppBarSeriesSettingsAction(seriesId: widget.seriesId),
|
||||
],
|
||||
);
|
||||
@@ -127,32 +130,34 @@ class _State extends State<_SonarrSeriesDetailsRoute> {
|
||||
if(tuple.item5) tuple.item4,
|
||||
]),
|
||||
builder: (context, AsyncSnapshot<List<Object>> snapshot) {
|
||||
if(_loaded && series == null) return LunaMessage.goBack(
|
||||
text: 'Series Not Found',
|
||||
context: context,
|
||||
);
|
||||
if(snapshot.hasError) {
|
||||
if(snapshot.connectionState != ConnectionState.waiting) {
|
||||
LunaLogger().error('Unable to pull Sonarr series details', snapshot.error, StackTrace.current);
|
||||
}
|
||||
return LSErrorMessage(onTapHandler: () => _refresh());
|
||||
return LunaMessage.error(onTap: loadCallback);
|
||||
}
|
||||
if(snapshot.hasData) {
|
||||
SonarrSeries series = _findSeries(snapshot.data[0]);
|
||||
if(series != null) {
|
||||
SonarrQualityProfile quality = _findQualityProfile(series.profileId, snapshot.data[2]);
|
||||
SonarrLanguageProfile language = Provider.of<SonarrState>(context, listen: false).enableVersion3
|
||||
? _findLanguageProfile(series.languageProfileId, snapshot.data[3])
|
||||
: null;
|
||||
List<SonarrTag> tags = _findTags(series.tags, snapshot.data[1]);
|
||||
return series == null
|
||||
? _unknown
|
||||
: PageView(
|
||||
controller: _pageController,
|
||||
children: _tabs(
|
||||
series: series,
|
||||
quality: quality,
|
||||
language: language,
|
||||
tags: tags,
|
||||
),
|
||||
);
|
||||
}
|
||||
if(series == null) return LunaLoader();
|
||||
SonarrQualityProfile quality = _findQualityProfile(series.profileId, snapshot.data[2]);
|
||||
SonarrLanguageProfile language = Provider.of<SonarrState>(context, listen: false).enableVersion3
|
||||
? _findLanguageProfile(series.languageProfileId, snapshot.data[3])
|
||||
: null;
|
||||
List<SonarrTag> tags = _findTags(series.tags, snapshot.data[1]);
|
||||
return series == null
|
||||
? _unknown
|
||||
: PageView(
|
||||
controller: _pageController,
|
||||
children: _tabs(
|
||||
series: series,
|
||||
quality: quality,
|
||||
language: language,
|
||||
tags: tags,
|
||||
),
|
||||
);
|
||||
}
|
||||
return LSLoader();
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export 'widgets/appbar_edit_action.dart';
|
||||
export 'widgets/appbar_settings_action.dart';
|
||||
export 'widgets/navigation_bar.dart';
|
||||
export 'widgets/overview.dart';
|
||||
export 'widgets/season_all_tile.dart';
|
||||
|
||||
@@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
import 'package:lunasea/modules/sonarr.dart';
|
||||
|
||||
class SonarrAppBarSeriesSettingsAction extends StatelessWidget {
|
||||
class SonarrAppBarSeriesEditAction extends StatelessWidget {
|
||||
final int seriesId;
|
||||
|
||||
SonarrAppBarSeriesSettingsAction({
|
||||
SonarrAppBarSeriesEditAction({
|
||||
Key key,
|
||||
@required this.seriesId,
|
||||
}) : super(key: key);
|
||||
@@ -21,119 +21,11 @@ class SonarrAppBarSeriesSettingsAction extends StatelessWidget {
|
||||
SonarrSeries series = snapshot.data.firstWhere((element) => element.id == seriesId, orElse: () => null);
|
||||
if(series != null) return LSIconButton(
|
||||
icon: Icons.edit,
|
||||
onPressed: () async => handler(context, series),
|
||||
onPressed: () async => SonarrSeriesEditRouter.navigateTo(context, seriesId: series.id),
|
||||
);
|
||||
}
|
||||
return Container();
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
static Future<void> handler(
|
||||
BuildContext context,
|
||||
SonarrSeries series,
|
||||
) async {
|
||||
List values = await SonarrDialogs.seriesSettings(context, series);
|
||||
if(values[0]) switch(values[1] as SonarrSeriesSettingsType) {
|
||||
case SonarrSeriesSettingsType.EDIT: _edit(context, series); break;
|
||||
case SonarrSeriesSettingsType.DELETE: _delete(context, series); break;
|
||||
case SonarrSeriesSettingsType.REFRESH: _refresh(context, series); break;
|
||||
case SonarrSeriesSettingsType.MONITORED: _monitored(context, series); break;
|
||||
default: LunaLogger().warning('SonarrAppBarSeriesSettingsAction', '_handler', 'Unknown case: ${(values[1] as SonarrSeriesSettingsType)}');
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> _edit(
|
||||
BuildContext context,
|
||||
SonarrSeries series,
|
||||
) async => SonarrSeriesEditRouter.navigateTo(context, seriesId: series.id);
|
||||
|
||||
static Future<void> _monitored(
|
||||
BuildContext context,
|
||||
SonarrSeries series,
|
||||
) async {
|
||||
SonarrState _state = context.read<SonarrState>();
|
||||
if(_state.api != null) {
|
||||
SonarrSeries _series = series.clone();
|
||||
_series.monitored = !_series.monitored;
|
||||
_state.api.series.updateSeries(series: _series)
|
||||
.then((_) {
|
||||
series.monitored = !series.monitored;
|
||||
_state.notify();
|
||||
LSSnackBar(
|
||||
context: context,
|
||||
title: series.monitored
|
||||
? 'Monitoring'
|
||||
: 'No Longer Monitoring',
|
||||
message: series.title,
|
||||
type: SNACKBAR_TYPE.success,
|
||||
);
|
||||
})
|
||||
.catchError((error, stack) {
|
||||
LunaLogger().error('Failed to toggle monitored state for series: ${series.id} / ${series.monitored}', error, stack);
|
||||
LSSnackBar(
|
||||
context: context,
|
||||
title: series.monitored
|
||||
? 'Failed to Unmonitor Series'
|
||||
: 'Failed to Monitor Series',
|
||||
type: SNACKBAR_TYPE.failure,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> _refresh(
|
||||
BuildContext context,
|
||||
SonarrSeries series,
|
||||
) async {
|
||||
Sonarr _sonarr = Provider.of<SonarrState>(context, listen: false).api;
|
||||
if(_sonarr != null) _sonarr.command.refreshSeries(seriesId: series.id)
|
||||
.then((_) {
|
||||
LSSnackBar(
|
||||
context: context,
|
||||
title: 'Refreshing...',
|
||||
message: series.title,
|
||||
);
|
||||
})
|
||||
.catchError((error, stack) {
|
||||
LunaLogger().error('Unable to refresh series: ${series.id}', error, stack);
|
||||
LSSnackBar(
|
||||
context: context,
|
||||
title: 'Failed to Refresh',
|
||||
type: SNACKBAR_TYPE.failure,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
static Future<void> _delete(
|
||||
BuildContext context,
|
||||
SonarrSeries series,
|
||||
) async {
|
||||
SonarrState _state = context.read<SonarrState>();
|
||||
List _values = await SonarrDialogs.confirmDeleteSeries(context);
|
||||
if(_state.api != null && _values[0]) _state.api.series.deleteSeries(
|
||||
seriesId: series.id,
|
||||
deleteFiles: _state.removeSeriesDeleteFiles,
|
||||
)
|
||||
.then((_) {
|
||||
LSSnackBar(
|
||||
context: context,
|
||||
title: _state.removeSeriesDeleteFiles
|
||||
? 'Series Removed (With Data)'
|
||||
: 'Series Removed',
|
||||
message: series.title,
|
||||
type: SNACKBAR_TYPE.success,
|
||||
);
|
||||
_state.reset();
|
||||
if(Navigator.of(context).canPop()) Navigator.of(context).pop();
|
||||
})
|
||||
.catchError((error, stack) {
|
||||
LunaLogger().error('Failed to remove series: ${series.id}', error, stack);
|
||||
LSSnackBar(
|
||||
context: context,
|
||||
title: 'Failed to Remove Series',
|
||||
type: SNACKBAR_TYPE.failure,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
import 'package:lunasea/modules/sonarr.dart';
|
||||
|
||||
class SonarrAppBarSeriesSettingsAction extends StatelessWidget {
|
||||
final int seriesId;
|
||||
|
||||
SonarrAppBarSeriesSettingsAction({
|
||||
Key key,
|
||||
@required this.seriesId,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Selector<SonarrState, Future<List<SonarrSeries>>>(
|
||||
selector: (_, state) => state.series,
|
||||
builder: (context, future, _) => FutureBuilder(
|
||||
future: future,
|
||||
builder: (context, AsyncSnapshot<List<SonarrSeries>> snapshot) {
|
||||
if(snapshot.hasError) return Container();
|
||||
if(snapshot.hasData) {
|
||||
SonarrSeries series = snapshot.data.firstWhere((element) => element.id == seriesId, orElse: () => null);
|
||||
if(series != null) return LSIconButton(
|
||||
icon: Icons.more_vert,
|
||||
onPressed: () async => handler(context, series),
|
||||
);
|
||||
}
|
||||
return Container();
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
static Future<void> handler(
|
||||
BuildContext context,
|
||||
SonarrSeries series,
|
||||
) async {
|
||||
List values = await SonarrDialogs.seriesSettings(context, series);
|
||||
if(values[0]) switch(values[1] as SonarrSeriesSettingsType) {
|
||||
case SonarrSeriesSettingsType.EDIT: _edit(context, series); break;
|
||||
case SonarrSeriesSettingsType.DELETE: _delete(context, series); break;
|
||||
case SonarrSeriesSettingsType.REFRESH: _refresh(context, series); break;
|
||||
case SonarrSeriesSettingsType.MONITORED: _monitored(context, series); break;
|
||||
default: LunaLogger().warning('SonarrAppBarSeriesSettingsAction', '_handler', 'Unknown case: ${(values[1] as SonarrSeriesSettingsType)}');
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> _edit(
|
||||
BuildContext context,
|
||||
SonarrSeries series,
|
||||
) async => SonarrSeriesEditRouter.navigateTo(context, seriesId: series.id);
|
||||
|
||||
static Future<void> _monitored(
|
||||
BuildContext context,
|
||||
SonarrSeries series,
|
||||
) async {
|
||||
SonarrState _state = context.read<SonarrState>();
|
||||
if(_state.api != null) {
|
||||
SonarrSeries _series = series.clone();
|
||||
_series.monitored = !_series.monitored;
|
||||
_state.api.series.updateSeries(series: _series)
|
||||
.then((_) {
|
||||
series.monitored = !series.monitored;
|
||||
_state.notify();
|
||||
LSSnackBar(
|
||||
context: context,
|
||||
title: series.monitored
|
||||
? 'Monitoring'
|
||||
: 'No Longer Monitoring',
|
||||
message: series.title,
|
||||
type: SNACKBAR_TYPE.success,
|
||||
);
|
||||
})
|
||||
.catchError((error, stack) {
|
||||
LunaLogger().error('Failed to toggle monitored state for series: ${series.id} / ${series.monitored}', error, stack);
|
||||
LSSnackBar(
|
||||
context: context,
|
||||
title: series.monitored
|
||||
? 'Failed to Unmonitor Series'
|
||||
: 'Failed to Monitor Series',
|
||||
type: SNACKBAR_TYPE.failure,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> _refresh(
|
||||
BuildContext context,
|
||||
SonarrSeries series,
|
||||
) async {
|
||||
Sonarr _sonarr = Provider.of<SonarrState>(context, listen: false).api;
|
||||
if(_sonarr != null) _sonarr.command.refreshSeries(seriesId: series.id)
|
||||
.then((_) {
|
||||
LSSnackBar(
|
||||
context: context,
|
||||
title: 'Refreshing...',
|
||||
message: series.title,
|
||||
);
|
||||
})
|
||||
.catchError((error, stack) {
|
||||
LunaLogger().error('Unable to refresh series: ${series.id}', error, stack);
|
||||
LSSnackBar(
|
||||
context: context,
|
||||
title: 'Failed to Refresh',
|
||||
type: SNACKBAR_TYPE.failure,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
static Future<void> _delete(
|
||||
BuildContext context,
|
||||
SonarrSeries series,
|
||||
) async {
|
||||
SonarrState _state = context.read<SonarrState>();
|
||||
List _values = await SonarrDialogs.confirmDeleteSeries(context);
|
||||
if(_state.api != null && _values[0]) _state.api.series.deleteSeries(
|
||||
seriesId: series.id,
|
||||
deleteFiles: _state.removeSeriesDeleteFiles,
|
||||
)
|
||||
.then((_) {
|
||||
LSSnackBar(
|
||||
context: context,
|
||||
title: _state.removeSeriesDeleteFiles
|
||||
? 'Series Removed (With Data)'
|
||||
: 'Series Removed',
|
||||
message: series.title,
|
||||
type: SNACKBAR_TYPE.success,
|
||||
);
|
||||
_state.reset();
|
||||
if(Navigator.of(context).canPop()) Navigator.of(context).pop();
|
||||
})
|
||||
.catchError((error, stack) {
|
||||
LunaLogger().error('Failed to remove series: ${series.id}', error, stack);
|
||||
LSSnackBar(
|
||||
context: context,
|
||||
title: 'Failed to Remove Series',
|
||||
type: SNACKBAR_TYPE.failure,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user