mirror of
https://github.com/jagandeepbrar/lunasea.git
synced 2026-08-30 20:24:25 +00:00
[Release] v2.3.0 (230004) (#220)
* [Packages] Updated packages * [Sonarr/Series] Switched to bottom navigation, removed sliver app bar * [Sonarr/Series] Removed bottom padding from overview * [README] Added AltStore, Removed Feedback, Updated Android APK Build (#213) * [Snackbar] Fixed cases where iOS swipe-back would lock UI * [Android/Back Button] Back button now shows drawer on primary routes instead of closing app * [Wake on LAN] Added configuration to hive * [Settings/Modules] Added Wake on LAN section * [Wake on LAN] Added entry to drawer * [Lidarr/Details] Removed sliver app bar, removed fanart, switched to bottom navigation bar * [Radarr/Details] Removed sliver app bar, removed fanart, switched to bottom navigation bar * [Wake on LAN] Show toast on action * [Wake on LAN] Created RawDatagramSocket binding * [WoL] Full support for Wake on LAN functionality * [Semantics] Cleanup * [TestFlight] v2.3.0 (230002) * [Settings/Modules] Implemented settings UI for custom headers * [Custom Headers] Finished UI implementation * [Custom Headers] Integrated into API calls * [TestFlight] v2.3.0 (230003) * [Images] Attach custom headers to all network image requests * [Everything] Many Changes (Read Notes) (#218) - Unified the structure of each module (core, routes, widgets at the root of every module) - Cleaned Most Remaining Dialogs (SABnzbd and NZBGet remain) - Reduced font-size across dialogs - Unified padding across all dialogs (that have been cleaned so far) - Changelog is now a routed page, not a dialog prompt - SABnzbd: Fixed bug where setting priority to "stop" would cause a fatal crash * [Networking] Added RegExp Validation for IPv4 & MAC Addresses * [Networking] Improved network address classes * Finished Dialog Rework * [TestFlight] v2.3.0 (230004) * [README] Updated README for v2.3.0
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -35,6 +35,7 @@ lib/generated_plugin_registrant.dart
|
||||
|
||||
# Exceptions to above rules.
|
||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||
ios/Flutter/.last_build_id
|
||||
|
||||
# LunaSea
|
||||
LunaSea.ipa
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
[](https://www.reddit.com/r/LunaSeaApp)
|
||||
[](https://altstore.lunasea.app)
|
||||
[](https://apps.apple.com/us/app/lunasea/id1496797802?ls=1)
|
||||
[](https://play.google.com/store/apps/details?id=app.lunasea.lunasea)
|
||||
[](https://apps.apple.com/us/app/lunasea/id1496797802?ls=1)
|
||||
[](https://play.google.com/store/apps/details?id=app.lunasea.lunasea)
|
||||
|
||||

|
||||
|
||||
@@ -16,6 +15,7 @@ LunaSea is a fully featured, open source self-hosted media manager! Focused on g
|
||||
- NZBGet
|
||||
- NZBHydra2
|
||||
- Newznab Indexer Searching
|
||||
- Wake on LAN
|
||||
|
||||
LunaSea even comes with support for multiple instances of applications using profiles, backing up and restoring your configuration to your filesystem, an AMOLED black theme, and more!
|
||||
|
||||
|
||||
@@ -1,6 +1,39 @@
|
||||
PODS:
|
||||
- DKImagePickerController/Core (4.3.1):
|
||||
- DKImagePickerController/ImageDataManager
|
||||
- DKImagePickerController/Resource
|
||||
- DKImagePickerController/ImageDataManager (4.3.1)
|
||||
- DKImagePickerController/PhotoGallery (4.3.1):
|
||||
- DKImagePickerController/Core
|
||||
- DKPhotoGallery
|
||||
- DKImagePickerController/Resource (4.3.1)
|
||||
- DKPhotoGallery (0.0.15):
|
||||
- DKPhotoGallery/Core (= 0.0.15)
|
||||
- DKPhotoGallery/Model (= 0.0.15)
|
||||
- DKPhotoGallery/Preview (= 0.0.15)
|
||||
- DKPhotoGallery/Resource (= 0.0.15)
|
||||
- SDWebImage
|
||||
- SDWebImageFLPlugin
|
||||
- DKPhotoGallery/Core (0.0.15):
|
||||
- DKPhotoGallery/Model
|
||||
- DKPhotoGallery/Preview
|
||||
- SDWebImage
|
||||
- SDWebImageFLPlugin
|
||||
- DKPhotoGallery/Model (0.0.15):
|
||||
- SDWebImage
|
||||
- SDWebImageFLPlugin
|
||||
- DKPhotoGallery/Preview (0.0.15):
|
||||
- DKPhotoGallery/Model
|
||||
- DKPhotoGallery/Resource
|
||||
- SDWebImage
|
||||
- SDWebImageFLPlugin
|
||||
- DKPhotoGallery/Resource (0.0.15):
|
||||
- SDWebImage
|
||||
- SDWebImageFLPlugin
|
||||
- file_picker (0.0.1):
|
||||
- DKImagePickerController/PhotoGallery
|
||||
- Flutter
|
||||
- FLAnimatedImage (1.0.12)
|
||||
- Flutter (1.0.0)
|
||||
- flutter_plugin_android_lifecycle (0.0.1):
|
||||
- Flutter
|
||||
@@ -14,6 +47,12 @@ PODS:
|
||||
- Flutter
|
||||
- "permission_handler (4.4.0+hotfix.2)":
|
||||
- Flutter
|
||||
- SDWebImage (5.8.0):
|
||||
- SDWebImage/Core (= 5.8.0)
|
||||
- SDWebImage/Core (5.8.0)
|
||||
- SDWebImageFLPlugin (0.4.0):
|
||||
- FLAnimatedImage (>= 1.0.11)
|
||||
- SDWebImage/Core (~> 5.6)
|
||||
- url_launcher (0.0.1):
|
||||
- Flutter
|
||||
- url_launcher_macos (0.0.1):
|
||||
@@ -34,6 +73,14 @@ DEPENDENCIES:
|
||||
- url_launcher_macos (from `.symlinks/plugins/url_launcher_macos/ios`)
|
||||
- url_launcher_web (from `.symlinks/plugins/url_launcher_web/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- DKImagePickerController
|
||||
- DKPhotoGallery
|
||||
- FLAnimatedImage
|
||||
- SDWebImage
|
||||
- SDWebImageFLPlugin
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
file_picker:
|
||||
:path: ".symlinks/plugins/file_picker/ios"
|
||||
@@ -59,15 +106,20 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/url_launcher_web/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
file_picker: 408623be2125b79a4539cf703be3d4b3abe5e245
|
||||
DKImagePickerController: f3d397887f38b98276a506b02ed9c167425f7cf3
|
||||
DKPhotoGallery: e880aef16c108333240e1e7327896f2ea380f4f0
|
||||
file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1
|
||||
FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31
|
||||
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
|
||||
flutter_plugin_android_lifecycle: 47de533a02850f070f5696a623995e93eddcdb9b
|
||||
in_app_purchase: 3e2155afa9d03d4fa32d9e62d567885080ce97d6
|
||||
package_info: 48b108e75b8802c2d5e126f208ef540561c98aef
|
||||
path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d
|
||||
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
|
||||
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
|
||||
path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0
|
||||
permission_handler: d5d46b2ca93671e8dcf870b949e304c7ddb65ad0
|
||||
url_launcher: a1c0cc845906122c4784c542523d8cacbded5626
|
||||
SDWebImage: 84000f962cbfa70c07f19d2234cbfcf5d779b5dc
|
||||
SDWebImageFLPlugin: 6c2295fb1242d44467c6c87dc5db6b0a13228fd8
|
||||
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
|
||||
url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313
|
||||
url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c
|
||||
|
||||
|
||||
@@ -314,7 +314,6 @@
|
||||
/* Begin XCBuildConfiguration section */
|
||||
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
@@ -396,7 +395,6 @@
|
||||
};
|
||||
97C147031CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
@@ -452,7 +450,6 @@
|
||||
};
|
||||
97C147041CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
|
||||
@@ -2,10 +2,12 @@ export './core/constants.dart';
|
||||
export './core/configuration.dart';
|
||||
export './core/data.dart';
|
||||
export './core/database.dart';
|
||||
export './core/dialogs.dart';
|
||||
export './core/extensions.dart';
|
||||
export './core/in_app_purchases.dart';
|
||||
export './core/logger.dart';
|
||||
export './core/module_flags.dart';
|
||||
export './core/networking.dart';
|
||||
export './core/providers.dart';
|
||||
export './core/routes.dart';
|
||||
export './core/theme.dart';
|
||||
|
||||
@@ -19,21 +19,25 @@ class Import {
|
||||
sonarrKey: profile['sonarrKey'] ?? '',
|
||||
sonarrStrictTLS: profile['sonarrStrictTLS'] ?? true,
|
||||
sonarrVersion3: profile['sonarrVersion3'] ?? false,
|
||||
sonarrHeaders: profile['sonarrHeaders'] ?? {},
|
||||
//Radarr
|
||||
radarrEnabled: profile['radarrEnabled'] ?? false,
|
||||
radarrHost: profile['radarrHost'] ?? '',
|
||||
radarrKey: profile['radarrKey'] ?? '',
|
||||
radarrStrictTLS: profile['radarrStrictTLS'] ?? true,
|
||||
radarrHeaders: profile['radarrHeaders'] ?? {},
|
||||
//Lidarr
|
||||
lidarrEnabled: profile['lidarrEnabled'] ?? false,
|
||||
lidarrHost: profile['lidarrHost'] ?? '',
|
||||
lidarrKey: profile['lidarrKey'] ?? '',
|
||||
lidarrStrictTLS: profile['lidarrStrictTLS'] ?? true,
|
||||
lidarrHeaders: profile['lidarrHeaders'] ?? {},
|
||||
//SABnzbd
|
||||
sabnzbdEnabled: profile['sabnzbdEnabled'] ?? false,
|
||||
sabnzbdHost: profile['sabnzbdHost'] ?? '',
|
||||
sabnzbdKey: profile['sabnzbdKey'] ?? '',
|
||||
sabnzbdStrictTLS: profile['sabnzbdStrictTLS'] ?? true,
|
||||
sabnzbdHeaders: profile['sabnzbdHeaders'] ?? {},
|
||||
//NZBGet
|
||||
nzbgetEnabled: profile['nzbgetEnabled'] ?? false,
|
||||
nzbgetHost: profile['nzbgetHost'] ?? '',
|
||||
@@ -41,6 +45,11 @@ class Import {
|
||||
nzbgetPass: profile['nzbgetPass'] ?? '',
|
||||
nzbgetStrictTLS: profile['nzbgetStrictTLS'] ?? true,
|
||||
nzbgetBasicAuth: profile['nzbgetBasicAuth'] ?? false,
|
||||
nzbgetHeaders: profile['nzbgetHeaders'] ?? {},
|
||||
//Wake on LAN
|
||||
wakeOnLANEnabled: profile['wakeOnLANEnabled'] ?? false,
|
||||
wakeOnLANBroadcastAddress: profile['wakeOnLANBroadcastAddress'] ?? '',
|
||||
wakeOnLANMACAddress: profile['wakeOnLANMACAddress'] ?? '',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,18 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
import 'package:lunasea/modules.dart';
|
||||
|
||||
class Constants {
|
||||
Constants._();
|
||||
//Services
|
||||
static const Map SERVICE_MAP = {
|
||||
'lidarr': LidarrConstants.SERVICE_MAP,
|
||||
'radarr': RadarrConstants.SERVICE_MAP,
|
||||
'sonarr': SonarrConstants.SERVICE_MAP,
|
||||
'nzbget': {
|
||||
'name': 'NZBGet',
|
||||
'desc': 'Manage Usenet Downloads',
|
||||
'icon': CustomIcons.nzbget,
|
||||
'route': '/nzbget',
|
||||
},
|
||||
'sabnzbd': {
|
||||
'name': 'SABnzbd',
|
||||
'desc': 'Manage Usenet Downloads',
|
||||
'icon': CustomIcons.sabnzbd,
|
||||
'route': '/sabnzbd',
|
||||
},
|
||||
'search': {
|
||||
'name': 'Search',
|
||||
'desc': 'Search Newznab Indexers',
|
||||
'icon': Icons.search,
|
||||
'route': '/search',
|
||||
},
|
||||
'settings': {
|
||||
'name': 'Settings',
|
||||
'desc': 'Update Configuration',
|
||||
'icon': Icons.settings,
|
||||
'route': '/settings',
|
||||
}
|
||||
static const Map MODULE_MAP = {
|
||||
LidarrConstants.MODULE_KEY: LidarrConstants.MODULE_MAP,
|
||||
RadarrConstants.MODULE_KEY: RadarrConstants.MODULE_MAP,
|
||||
SonarrConstants.MODULE_KEY: SonarrConstants.MODULE_MAP,
|
||||
NZBGetConstants.MODULE_KEY: NZBGetConstants.MODULE_MAP,
|
||||
SABnzbdConstants.MODULE_KEY: SABnzbdConstants.MODULE_MAP,
|
||||
SearchConstants.MODULE_KEY: SearchConstants.MODULE_MAP,
|
||||
SettingsConstants.MODULE_KEY: SettingsConstants.MODULE_MAP,
|
||||
WakeOnLANConstants.MODULE_KEY: WakeOnLANConstants.MODULE_MAP,
|
||||
};
|
||||
//Colors
|
||||
static const PRIMARY_COLOR = 0xFF32323E;
|
||||
@@ -42,15 +22,16 @@ class Constants {
|
||||
static const LIST_COLOR_ICONS = [
|
||||
Colors.blue,
|
||||
Color(ACCENT_COLOR),
|
||||
Colors.orange,
|
||||
Colors.red,
|
||||
Colors.orange,
|
||||
Colors.deepPurpleAccent,
|
||||
Colors.blueGrey,
|
||||
];
|
||||
//UI
|
||||
static const UI_ELEVATION = 2.0;
|
||||
static const UI_ELEVATION = 0.0;
|
||||
static const UI_CARD_MARGIN = EdgeInsets.symmetric(horizontal: 12.0, vertical: 6.0);
|
||||
static const UI_NAVIGATION_SPEED = 250;
|
||||
static const UI_BORDER_RADIUS = 12.0;
|
||||
//General
|
||||
static const EMPTY_MAP = {};
|
||||
static const EMPTY_LIST = [];
|
||||
@@ -73,6 +54,7 @@ class Constants {
|
||||
//Changelog
|
||||
static const EMPTY_CHANGELOG = [{
|
||||
"version": "Changelog Error",
|
||||
"date": "Unknown Date",
|
||||
"new": ['Unable to fetch changes'],
|
||||
"fixes": ['Unable to fetch changes'],
|
||||
"tweaks": ['Unable to fetch changes'],
|
||||
|
||||
@@ -4,6 +4,8 @@ import 'package:lunasea/core.dart';
|
||||
|
||||
part 'profile.g.dart';
|
||||
|
||||
//Next HiveField ID: 31
|
||||
|
||||
@HiveType(typeId: 0, adapterName: 'ProfileHiveObjectAdapter')
|
||||
class ProfileHiveObject extends HiveObject {
|
||||
factory ProfileHiveObject.empty() {
|
||||
@@ -13,22 +15,26 @@ class ProfileHiveObject extends HiveObject {
|
||||
lidarrHost: '',
|
||||
lidarrKey: '',
|
||||
lidarrStrictTLS: true,
|
||||
lidarrHeaders: {},
|
||||
//Radarr
|
||||
radarrEnabled: false,
|
||||
radarrHost: '',
|
||||
radarrKey: '',
|
||||
radarrStrictTLS: true,
|
||||
radarrHeaders: {},
|
||||
//Sonarr
|
||||
sonarrEnabled: false,
|
||||
sonarrHost: '',
|
||||
sonarrKey: '',
|
||||
sonarrStrictTLS: true,
|
||||
sonarrVersion3: false,
|
||||
sonarrHeaders: {},
|
||||
//SABnzbd
|
||||
sabnzbdEnabled: false,
|
||||
sabnzbdHost: '',
|
||||
sabnzbdKey: '',
|
||||
sabnzbdStrictTLS: true,
|
||||
sabnzbdHeaders: {},
|
||||
//NZBGet
|
||||
nzbgetEnabled: false,
|
||||
nzbgetHost: '',
|
||||
@@ -36,6 +42,11 @@ class ProfileHiveObject extends HiveObject {
|
||||
nzbgetPass: '',
|
||||
nzbgetStrictTLS: true,
|
||||
nzbgetBasicAuth: false,
|
||||
nzbgetHeaders: {},
|
||||
//Wake on LAN
|
||||
wakeOnLANEnabled: false,
|
||||
wakeOnLANBroadcastAddress: '',
|
||||
wakeOnLANMACAddress: '',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -46,22 +57,26 @@ class ProfileHiveObject extends HiveObject {
|
||||
lidarrHost: obj.lidarrHost,
|
||||
lidarrKey: obj.lidarrKey,
|
||||
lidarrStrictTLS: obj.lidarrStrictTLS,
|
||||
lidarrHeaders: obj.lidarrHeaders,
|
||||
//Radarr
|
||||
radarrEnabled: obj.radarrEnabled,
|
||||
radarrHost: obj.radarrHost,
|
||||
radarrKey: obj.radarrKey,
|
||||
radarrStrictTLS: obj.radarrStrictTLS,
|
||||
radarrHeaders: obj.radarrHeaders,
|
||||
//Sonarr
|
||||
sonarrEnabled: obj.sonarrEnabled,
|
||||
sonarrHost: obj.sonarrHost,
|
||||
sonarrKey: obj.sonarrKey,
|
||||
sonarrStrictTLS: obj.sonarrStrictTLS,
|
||||
sonarrVersion3: obj.sonarrVersion3,
|
||||
sonarrHeaders: obj.sonarrHeaders,
|
||||
//SABnzbd
|
||||
sabnzbdEnabled: obj.sabnzbdEnabled,
|
||||
sabnzbdHost: obj.sabnzbdHost,
|
||||
sabnzbdKey: obj.sabnzbdKey,
|
||||
sabnzbdStrictTLS: obj.sabnzbdStrictTLS,
|
||||
sabnzbdHeaders: obj.sabnzbdHeaders,
|
||||
//NZBGet
|
||||
nzbgetEnabled: obj.nzbgetEnabled,
|
||||
nzbgetHost: obj.nzbgetHost,
|
||||
@@ -69,6 +84,11 @@ class ProfileHiveObject extends HiveObject {
|
||||
nzbgetPass: obj.nzbgetPass,
|
||||
nzbgetStrictTLS: obj.nzbgetStrictTLS,
|
||||
nzbgetBasicAuth: obj.nzbgetBasicAuth,
|
||||
nzbgetHeaders: obj.nzbgetHeaders,
|
||||
//Wake On LAN
|
||||
wakeOnLANEnabled: obj.wakeOnLANEnabled,
|
||||
wakeOnLANBroadcastAddress: obj.wakeOnLANBroadcastAddress,
|
||||
wakeOnLANMACAddress: obj.wakeOnLANMACAddress,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -78,22 +98,26 @@ class ProfileHiveObject extends HiveObject {
|
||||
@required this.lidarrHost,
|
||||
@required this.lidarrKey,
|
||||
@required this.lidarrStrictTLS,
|
||||
@required this.lidarrHeaders,
|
||||
//Radarr
|
||||
@required this.radarrEnabled,
|
||||
@required this.radarrHost,
|
||||
@required this.radarrKey,
|
||||
@required this.radarrStrictTLS,
|
||||
@required this.radarrHeaders,
|
||||
//Sonarr
|
||||
@required this.sonarrEnabled,
|
||||
@required this.sonarrHost,
|
||||
@required this.sonarrKey,
|
||||
@required this.sonarrStrictTLS,
|
||||
@required this.sonarrVersion3,
|
||||
@required this.sonarrHeaders,
|
||||
//SABnzbd
|
||||
@required this.sabnzbdEnabled,
|
||||
@required this.sabnzbdHost,
|
||||
@required this.sabnzbdKey,
|
||||
@required this.sabnzbdStrictTLS,
|
||||
@required this.sabnzbdHeaders,
|
||||
//NZBGet
|
||||
@required this.nzbgetEnabled,
|
||||
@required this.nzbgetHost,
|
||||
@@ -101,6 +125,11 @@ class ProfileHiveObject extends HiveObject {
|
||||
@required this.nzbgetPass,
|
||||
@required this.nzbgetStrictTLS,
|
||||
@required this.nzbgetBasicAuth,
|
||||
@required this.nzbgetHeaders,
|
||||
//Wake On LAN
|
||||
@required this.wakeOnLANEnabled,
|
||||
@required this.wakeOnLANBroadcastAddress,
|
||||
@required this.wakeOnLANMACAddress,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -117,21 +146,25 @@ class ProfileHiveObject extends HiveObject {
|
||||
"sonarrKey": sonarrKey,
|
||||
"sonarrStrictTLS": sonarrStrictTLS,
|
||||
"sonarrVersion3": sonarrVersion3,
|
||||
"sonarrHeaders": sonarrHeaders,
|
||||
//Radarr
|
||||
"radarrEnabled": radarrEnabled,
|
||||
"radarrHost": radarrHost,
|
||||
"radarrKey": radarrKey,
|
||||
"radarrStrictTLS": radarrStrictTLS,
|
||||
"radarrHeaders": radarrHeaders,
|
||||
//Lidarr
|
||||
"lidarrEnabled": lidarrEnabled,
|
||||
"lidarrHost": lidarrHost,
|
||||
"lidarrKey": lidarrKey,
|
||||
"lidarrStrictTLS": lidarrStrictTLS,
|
||||
"lidarrHeaders": lidarrHeaders,
|
||||
//SABnzbd
|
||||
"sabnzbdEnabled": sabnzbdEnabled,
|
||||
"sabnzbdHost": sabnzbdHost,
|
||||
"sabnzbdKey": sabnzbdKey,
|
||||
"sabnzbdStrictTLS": sabnzbdStrictTLS,
|
||||
"sabnzbdHeaders": sabnzbdHeaders,
|
||||
//NZBGet
|
||||
"nzbgetEnabled": nzbgetEnabled,
|
||||
"nzbgetHost": nzbgetHost,
|
||||
@@ -139,6 +172,11 @@ class ProfileHiveObject extends HiveObject {
|
||||
"nzbgetPass": nzbgetPass,
|
||||
"nzbgetStrictTLS": nzbgetStrictTLS,
|
||||
"nzbgetBasicAuth": nzbgetBasicAuth,
|
||||
"nzbgetHeaders": nzbgetHeaders,
|
||||
//Wake On LAN
|
||||
"wakeOnLANEnabled": wakeOnLANEnabled,
|
||||
"wakeOnLANBroadcastAddress": wakeOnLANBroadcastAddress,
|
||||
"wakeOnLANMACAddress": wakeOnLANMACAddress,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -151,12 +189,15 @@ class ProfileHiveObject extends HiveObject {
|
||||
String lidarrKey;
|
||||
@HiveField(18)
|
||||
bool lidarrStrictTLS;
|
||||
@HiveField(26)
|
||||
Map lidarrHeaders;
|
||||
|
||||
Map<String, dynamic> getLidarr() => {
|
||||
'enabled': lidarrEnabled ?? false,
|
||||
'host': lidarrHost ?? '',
|
||||
'key': lidarrKey ?? '',
|
||||
'strict_tls': lidarrStrictTLS ?? true,
|
||||
'headers': lidarrHeaders ?? {},
|
||||
};
|
||||
|
||||
//Radarr
|
||||
@@ -168,12 +209,15 @@ class ProfileHiveObject extends HiveObject {
|
||||
String radarrKey;
|
||||
@HiveField(17)
|
||||
bool radarrStrictTLS;
|
||||
@HiveField(27)
|
||||
Map radarrHeaders;
|
||||
|
||||
Map<String, dynamic> getRadarr() => {
|
||||
'enabled': radarrEnabled ?? false,
|
||||
'host': radarrHost ?? '',
|
||||
'key': radarrKey ?? '',
|
||||
'strict_tls': radarrStrictTLS ?? true,
|
||||
'headers': radarrHeaders ?? {},
|
||||
};
|
||||
|
||||
//Sonarr
|
||||
@@ -187,6 +231,8 @@ class ProfileHiveObject extends HiveObject {
|
||||
bool sonarrStrictTLS;
|
||||
@HiveField(21)
|
||||
bool sonarrVersion3;
|
||||
@HiveField(28)
|
||||
Map sonarrHeaders;
|
||||
|
||||
Map<String, dynamic> getSonarr() => {
|
||||
'enabled': sonarrEnabled ?? false,
|
||||
@@ -194,6 +240,7 @@ class ProfileHiveObject extends HiveObject {
|
||||
'key': sonarrKey ?? '',
|
||||
'strict_tls': sonarrStrictTLS ?? true,
|
||||
'v3': sonarrVersion3 ?? false,
|
||||
'headers': sonarrHeaders ?? {},
|
||||
};
|
||||
|
||||
//SABnzbd
|
||||
@@ -205,12 +252,15 @@ class ProfileHiveObject extends HiveObject {
|
||||
String sabnzbdKey;
|
||||
@HiveField(19)
|
||||
bool sabnzbdStrictTLS;
|
||||
@HiveField(29)
|
||||
Map sabnzbdHeaders;
|
||||
|
||||
Map<String, dynamic> getSABnzbd() => {
|
||||
'enabled': sabnzbdEnabled ?? false,
|
||||
'host': sabnzbdHost ?? '',
|
||||
'key': sabnzbdKey ?? '',
|
||||
'strict_tls': sabnzbdStrictTLS ?? true,
|
||||
'headers': sabnzbdHeaders ?? {},
|
||||
};
|
||||
|
||||
//NZBGet
|
||||
@@ -226,6 +276,8 @@ class ProfileHiveObject extends HiveObject {
|
||||
bool nzbgetStrictTLS;
|
||||
@HiveField(22)
|
||||
bool nzbgetBasicAuth;
|
||||
@HiveField(30)
|
||||
Map nzbgetHeaders;
|
||||
|
||||
Map<String, dynamic> getNZBGet() => {
|
||||
'enabled': nzbgetEnabled ?? false,
|
||||
@@ -234,6 +286,21 @@ class ProfileHiveObject extends HiveObject {
|
||||
'pass': nzbgetPass ?? '',
|
||||
'strict_tls': nzbgetStrictTLS ?? true,
|
||||
'basic_auth': nzbgetBasicAuth ?? false,
|
||||
'headers': nzbgetHeaders ?? {},
|
||||
};
|
||||
|
||||
//Wake On LAN
|
||||
@HiveField(23)
|
||||
bool wakeOnLANEnabled;
|
||||
@HiveField(24)
|
||||
String wakeOnLANBroadcastAddress;
|
||||
@HiveField(25)
|
||||
String wakeOnLANMACAddress;
|
||||
|
||||
Map<String, dynamic> getWakeOnLAN() => {
|
||||
'enabled': wakeOnLANEnabled ?? false,
|
||||
'broadcastAddress': wakeOnLANBroadcastAddress ?? '',
|
||||
'MACAddress': wakeOnLANMACAddress ?? '',
|
||||
};
|
||||
|
||||
List<String> get enabledServices => [
|
||||
|
||||
@@ -21,32 +21,40 @@ class ProfileHiveObjectAdapter extends TypeAdapter<ProfileHiveObject> {
|
||||
lidarrHost: fields[1] as String,
|
||||
lidarrKey: fields[2] as String,
|
||||
lidarrStrictTLS: fields[18] as bool,
|
||||
lidarrHeaders: (fields[26] as Map)?.cast<dynamic, dynamic>(),
|
||||
radarrEnabled: fields[3] as bool,
|
||||
radarrHost: fields[4] as String,
|
||||
radarrKey: fields[5] as String,
|
||||
radarrStrictTLS: fields[17] as bool,
|
||||
radarrHeaders: (fields[27] as Map)?.cast<dynamic, dynamic>(),
|
||||
sonarrEnabled: fields[6] as bool,
|
||||
sonarrHost: fields[7] as String,
|
||||
sonarrKey: fields[8] as String,
|
||||
sonarrStrictTLS: fields[16] as bool,
|
||||
sonarrVersion3: fields[21] as bool,
|
||||
sonarrHeaders: (fields[28] as Map)?.cast<dynamic, dynamic>(),
|
||||
sabnzbdEnabled: fields[9] as bool,
|
||||
sabnzbdHost: fields[10] as String,
|
||||
sabnzbdKey: fields[11] as String,
|
||||
sabnzbdStrictTLS: fields[19] as bool,
|
||||
sabnzbdHeaders: (fields[29] as Map)?.cast<dynamic, dynamic>(),
|
||||
nzbgetEnabled: fields[12] as bool,
|
||||
nzbgetHost: fields[13] as String,
|
||||
nzbgetUser: fields[14] as String,
|
||||
nzbgetPass: fields[15] as String,
|
||||
nzbgetStrictTLS: fields[20] as bool,
|
||||
nzbgetBasicAuth: fields[22] as bool,
|
||||
nzbgetHeaders: (fields[30] as Map)?.cast<dynamic, dynamic>(),
|
||||
wakeOnLANEnabled: fields[23] as bool,
|
||||
wakeOnLANBroadcastAddress: fields[24] as String,
|
||||
wakeOnLANMACAddress: fields[25] as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, ProfileHiveObject obj) {
|
||||
writer
|
||||
..writeByte(23)
|
||||
..writeByte(31)
|
||||
..writeByte(0)
|
||||
..write(obj.lidarrEnabled)
|
||||
..writeByte(1)
|
||||
@@ -55,6 +63,8 @@ class ProfileHiveObjectAdapter extends TypeAdapter<ProfileHiveObject> {
|
||||
..write(obj.lidarrKey)
|
||||
..writeByte(18)
|
||||
..write(obj.lidarrStrictTLS)
|
||||
..writeByte(26)
|
||||
..write(obj.lidarrHeaders)
|
||||
..writeByte(3)
|
||||
..write(obj.radarrEnabled)
|
||||
..writeByte(4)
|
||||
@@ -63,6 +73,8 @@ class ProfileHiveObjectAdapter extends TypeAdapter<ProfileHiveObject> {
|
||||
..write(obj.radarrKey)
|
||||
..writeByte(17)
|
||||
..write(obj.radarrStrictTLS)
|
||||
..writeByte(27)
|
||||
..write(obj.radarrHeaders)
|
||||
..writeByte(6)
|
||||
..write(obj.sonarrEnabled)
|
||||
..writeByte(7)
|
||||
@@ -73,6 +85,8 @@ class ProfileHiveObjectAdapter extends TypeAdapter<ProfileHiveObject> {
|
||||
..write(obj.sonarrStrictTLS)
|
||||
..writeByte(21)
|
||||
..write(obj.sonarrVersion3)
|
||||
..writeByte(28)
|
||||
..write(obj.sonarrHeaders)
|
||||
..writeByte(9)
|
||||
..write(obj.sabnzbdEnabled)
|
||||
..writeByte(10)
|
||||
@@ -81,6 +95,8 @@ class ProfileHiveObjectAdapter extends TypeAdapter<ProfileHiveObject> {
|
||||
..write(obj.sabnzbdKey)
|
||||
..writeByte(19)
|
||||
..write(obj.sabnzbdStrictTLS)
|
||||
..writeByte(29)
|
||||
..write(obj.sabnzbdHeaders)
|
||||
..writeByte(12)
|
||||
..write(obj.nzbgetEnabled)
|
||||
..writeByte(13)
|
||||
@@ -92,6 +108,14 @@ class ProfileHiveObjectAdapter extends TypeAdapter<ProfileHiveObject> {
|
||||
..writeByte(20)
|
||||
..write(obj.nzbgetStrictTLS)
|
||||
..writeByte(22)
|
||||
..write(obj.nzbgetBasicAuth);
|
||||
..write(obj.nzbgetBasicAuth)
|
||||
..writeByte(30)
|
||||
..write(obj.nzbgetHeaders)
|
||||
..writeByte(23)
|
||||
..write(obj.wakeOnLANEnabled)
|
||||
..writeByte(24)
|
||||
..write(obj.wakeOnLANBroadcastAddress)
|
||||
..writeByte(25)
|
||||
..write(obj.wakeOnLANMACAddress);
|
||||
}
|
||||
}
|
||||
|
||||
96
lib/core/dialogs.dart
Normal file
96
lib/core/dialogs.dart
Normal file
@@ -0,0 +1,96 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
|
||||
class GlobalDialogs {
|
||||
GlobalDialogs._();
|
||||
|
||||
static Future<List<dynamic>> editText(BuildContext context, String title, { String prefill = '' }) async {
|
||||
bool _flag = false;
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _textController = TextEditingController()..text = prefill;
|
||||
|
||||
void _setValues(bool flag) {
|
||||
if(_formKey.currentState.validate()) {
|
||||
_flag = flag;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: title,
|
||||
buttons: [
|
||||
LSDialog.button(
|
||||
text: 'Save',
|
||||
onPressed: () => _setValues(true),
|
||||
),
|
||||
],
|
||||
content: [
|
||||
Form(
|
||||
key: _formKey,
|
||||
child: LSDialog.textFormInput(
|
||||
controller: _textController,
|
||||
title: title,
|
||||
onSubmitted: (_) => _setValues(true),
|
||||
validator: (_) => null,
|
||||
),
|
||||
),
|
||||
],
|
||||
contentPadding: LSDialog.inputDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _textController.text];
|
||||
}
|
||||
|
||||
static Future<void> textPreview(BuildContext context, String title, String text, { bool alignLeft = false }) async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
actions: <Widget>[
|
||||
LSDialog.button(
|
||||
text: 'Close',
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
],
|
||||
title: LSDialog.title(text: title),
|
||||
content: LSDialog.content(
|
||||
children: [
|
||||
LSDialog.textContent(
|
||||
text: text,
|
||||
textAlign: alignLeft
|
||||
? TextAlign.left
|
||||
: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
contentPadding: LSDialog.textDialogContentPadding(),
|
||||
shape: LSDialog.shape(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> deleteCatalogueWithFiles(BuildContext context, String title) async {
|
||||
bool _flag = false;
|
||||
|
||||
void _setValues(bool flag) {
|
||||
_flag = flag;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Delete All Files',
|
||||
buttons: [
|
||||
LSDialog.button(
|
||||
text: 'Delete',
|
||||
textColor: LSColors.red,
|
||||
onPressed: () => _setValues(true),
|
||||
),
|
||||
],
|
||||
content: [
|
||||
LSDialog.textContent(text: 'Are you sure you want to delete all the files and folders for $title?'),
|
||||
],
|
||||
contentPadding: LSDialog.textDialogContentPadding(),
|
||||
);
|
||||
return [_flag];
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
class ModuleFlags {
|
||||
ModuleFlags._();
|
||||
//General
|
||||
static const SEARCH = true;
|
||||
static const WAKE_ON_LAN = true;
|
||||
//Automation
|
||||
static const AUTOMATION = true;
|
||||
static const LIDARR = true;
|
||||
static const RADARR = true;
|
||||
static const SONARR = true;
|
||||
//Search
|
||||
static const SEARCH = true;
|
||||
//Clients
|
||||
static const CLIENTS = true;
|
||||
static const SABNZBD = true;
|
||||
|
||||
2
lib/core/networking.dart
Normal file
2
lib/core/networking.dart
Normal file
@@ -0,0 +1,2 @@
|
||||
export './networking/ipv4_address.dart';
|
||||
export './networking/mac_address.dart';
|
||||
18
lib/core/networking/ipv4_address.dart
Normal file
18
lib/core/networking/ipv4_address.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
class IPv4Address {
|
||||
static const String _REGEX = r"\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}\b";
|
||||
final String _address;
|
||||
|
||||
IPv4Address._internal(this._address);
|
||||
factory IPv4Address.from(String address) {
|
||||
if(!IPv4Address.validate(address)) throw FormatException('Not a valid IPv4 address string');
|
||||
return IPv4Address._internal(address);
|
||||
}
|
||||
|
||||
String get address => _address;
|
||||
|
||||
/// Validate that an IPv4 address has been formatted correctly
|
||||
static bool validate(String address) {
|
||||
RegExp exp = new RegExp(_REGEX);
|
||||
return exp.hasMatch(address);
|
||||
}
|
||||
}
|
||||
24
lib/core/networking/mac_address.dart
Normal file
24
lib/core/networking/mac_address.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
import 'package:convert/convert.dart';
|
||||
|
||||
class MacAddress {
|
||||
static const String _REGEX = r"^([0-9A-Fa-f]{2}[:]){5}([0-9A-Fa-f]{2})$";
|
||||
final String _address;
|
||||
|
||||
MacAddress._internal(this._address);
|
||||
factory MacAddress.from(String address) {
|
||||
if(!MacAddress.validate(address)) throw FormatException('Not a valid MAC address string');
|
||||
return MacAddress._internal(address);
|
||||
}
|
||||
|
||||
String get address => _address;
|
||||
List<int> get bytes => _getBytes();
|
||||
|
||||
//Parse the address string and returns an int array list of 6 values, each containing one block of the
|
||||
List<int> _getBytes() => _address.split(":").map((octet) => hex.decode(octet)[0]).toList();
|
||||
|
||||
/// Validate that a MAC address has been formatted correctly
|
||||
static bool validate(String address) {
|
||||
RegExp exp = new RegExp(_REGEX);
|
||||
return exp.hasMatch(address);
|
||||
}
|
||||
}
|
||||
@@ -42,15 +42,21 @@ class Routes {
|
||||
// /settings/modules/*
|
||||
// Automation
|
||||
SettingsModulesLidarr.ROUTE_NAME: (context) => SettingsModulesLidarr(),
|
||||
SettingsModulesLidarrHeaders.ROUTE_NAME: (context) => SettingsModulesLidarrHeaders(),
|
||||
SettingsModulesRadarr.ROUTE_NAME: (context) => SettingsModulesRadarr(),
|
||||
SettingsModulesRadarrHeaders.ROUTE_NAME: (context) => SettingsModulesRadarrHeaders(),
|
||||
SettingsModulesSonarr.ROUTE_NAME: (context) => SettingsModulesSonarr(),
|
||||
SettingsModulesSonarrHeaders.ROUTE_NAME: (context) => SettingsModulesSonarrHeaders(),
|
||||
// Clients
|
||||
SettingsModulesNZBGet.ROUTE_NAME: (context) => SettingsModulesNZBGet(),
|
||||
SettingsModulesNZBGetHeaders.ROUTE_NAME: (context) => SettingsModulesNZBGetHeaders(),
|
||||
SettingsModulesSABnzbd.ROUTE_NAME: (context) => SettingsModulesSABnzbd(),
|
||||
SettingsModulesSABnzbdHeaders.ROUTE_NAME: (context) => SettingsModulesSABnzbdHeaders(),
|
||||
// General
|
||||
SettingsModulesCalendar.ROUTE_NAME: (context) => SettingsModulesCalendar(),
|
||||
SettingsModulesLunaSea.ROUTE_NAME: (context) => SettingsModulesLunaSea(),
|
||||
SettingsModulesSearch.ROUTE_NAME: (context) => SettingsModulesSearch(),
|
||||
SettingsModulesWakeOnLAN.ROUTE_NAME: (context) => SettingsModulesWakeOnLAN(),
|
||||
// /settings/modules/indexers/*
|
||||
SettingsModulesSearchAdd.ROUTE_NAME: (context) => SettingsModulesSearchAdd(),
|
||||
SettingsModulesSearchEdit.ROUTE_NAME: (context) => SettingsModulesSearchEdit(),
|
||||
@@ -59,6 +65,8 @@ class Routes {
|
||||
SettingsGeneralLogsView.ROUTE_NAME: (context) => SettingsGeneralLogsView(),
|
||||
SettingsGeneralLogsDetails.ROUTE_NAME: (context) => SettingsGeneralLogsDetails(),
|
||||
// /settings/system/*
|
||||
SettingsSystemChangelog.ROUTE_NAME: (context) => SettingsSystemChangelog(),
|
||||
SettingsSystemChangelogDetails.ROUTE_NAME: (context) => SettingsSystemChangelogDetails(),
|
||||
SettingsSystemDonations.ROUTE_NAME: (context) => SettingsSystemDonations(),
|
||||
SettingsSystemDonationsThankYou.ROUTE_NAME: (context) => SettingsSystemDonationsThankYou(),
|
||||
};
|
||||
|
||||
@@ -3,11 +3,12 @@ import 'package:flutter_advanced_networkimage/provider.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
Decoration LSCardBackground({ @required String uri, bool darken = false }) => BoxDecoration(
|
||||
Decoration LSCardBackground({ @required String uri, @required Map headers, bool darken = false }) => BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AdvancedNetworkImage(
|
||||
uri,
|
||||
useDiskCache: true,
|
||||
header: Map<String, String>.from(headers),
|
||||
loadFailedCallback: () {},
|
||||
fallbackAssetImage: 'assets/images/colors/secondary.png',
|
||||
retryLimit: 1,
|
||||
|
||||
@@ -8,12 +8,14 @@ class LSDescriptionBlock extends StatefulWidget {
|
||||
final String title;
|
||||
final String uri;
|
||||
final String fallbackImage;
|
||||
final Map headers;
|
||||
|
||||
LSDescriptionBlock({
|
||||
@required this.description,
|
||||
@required this.title,
|
||||
@required this.uri,
|
||||
@required this.fallbackImage,
|
||||
@required this.headers,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -32,6 +34,7 @@ class _State extends State<LSDescriptionBlock> {
|
||||
child: TransitionToImage(
|
||||
image: AdvancedNetworkImage(
|
||||
widget.uri,
|
||||
header: Map<String, String>.from(widget.headers),
|
||||
useDiskCache: true,
|
||||
fallbackAssetImage: widget.fallbackImage,
|
||||
retryLimit: 1,
|
||||
@@ -67,7 +70,7 @@ class _State extends State<LSDescriptionBlock> {
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.all(Radius.circular(4.0)),
|
||||
onTap: () => LSDialogSystem.textPreview(context, widget.title, widget.description.trim() ?? 'No summary is available.'),
|
||||
onTap: () => GlobalDialogs.textPreview(context, widget.title, widget.description.trim() ?? 'No summary is available.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
//Export
|
||||
export './dialogs/system.dart';
|
||||
export './dialogs/lidarr.dart';
|
||||
export './dialogs/radarr.dart';
|
||||
export './dialogs/sonarr.dart';
|
||||
export './dialogs/nzbget.dart';
|
||||
export './dialogs/sabnzbd.dart';
|
||||
export './dialogs/settings.dart';
|
||||
export './dialogs/search.dart';
|
||||
//Import
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
|
||||
@@ -16,14 +6,38 @@ abstract class LSDialog {
|
||||
text,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
);
|
||||
|
||||
static Widget button({ @required String text, @required void Function() onPressed, Color textColor = Colors.white }) => FlatButton(
|
||||
static TextSpan bolded({ @required String text, double fontSize = 14.0, Color color }) => TextSpan(
|
||||
text: text,
|
||||
style: TextStyle(
|
||||
color: color == null
|
||||
? LSColors.accent
|
||||
: color,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: fontSize,
|
||||
),
|
||||
);
|
||||
|
||||
static Widget richText({ @required List<TextSpan> children, TextAlign alignment = TextAlign.start }) => RichText(
|
||||
text: TextSpan(
|
||||
children: children,
|
||||
),
|
||||
textAlign: alignment,
|
||||
);
|
||||
|
||||
static Widget button({ @required String text, @required void Function() onPressed, Color textColor }) => FlatButton(
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(color: textColor),
|
||||
style: TextStyle(
|
||||
color: textColor == null
|
||||
? LSColors.accent
|
||||
: textColor,
|
||||
fontSize: 12.0,
|
||||
),
|
||||
),
|
||||
onPressed: onPressed,
|
||||
);
|
||||
@@ -31,7 +45,10 @@ abstract class LSDialog {
|
||||
static Widget cancel(BuildContext context, { Color textColor = Colors.white }) => FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(color: textColor),
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: 12.0,
|
||||
),
|
||||
),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
);
|
||||
@@ -40,13 +57,14 @@ abstract class LSDialog {
|
||||
child: ListBody(
|
||||
children: children,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
padding: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 1.0),
|
||||
);
|
||||
|
||||
static Widget textContent({ @required String text, TextAlign textAlign = TextAlign.center }) => Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
textAlign: textAlign,
|
||||
);
|
||||
@@ -55,7 +73,7 @@ abstract class LSDialog {
|
||||
text: text,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16.0,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -72,48 +90,123 @@ abstract class LSDialog {
|
||||
labelStyle: TextStyle(
|
||||
color: Colors.white54,
|
||||
decoration: TextDecoration.none,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: LSColors.accent),
|
||||
),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: LSColors.accent),
|
||||
borderSide: BorderSide(color: LSColors.accent.withOpacity(0.3)),
|
||||
),
|
||||
),
|
||||
style: TextStyle(color: Colors.white),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
cursorColor: LSColors.accent,
|
||||
textInputAction: TextInputAction.done,
|
||||
onSubmitted: onSubmitted,
|
||||
);
|
||||
|
||||
static Widget textFormInput({
|
||||
@required TextEditingController controller,
|
||||
@required String title,
|
||||
@required Function(String) onSubmitted,
|
||||
@required Function(String) validator,
|
||||
bool obscureText = false,
|
||||
TextInputType keyboardType = TextInputType.text,
|
||||
}) => TextFormField(
|
||||
autofocus: true,
|
||||
autocorrect: false,
|
||||
controller: controller,
|
||||
obscureText: obscureText,
|
||||
keyboardType: keyboardType,
|
||||
decoration: InputDecoration(
|
||||
labelText: title,
|
||||
labelStyle: TextStyle(
|
||||
color: Colors.white54,
|
||||
decoration: TextDecoration.none,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: LSColors.accent),
|
||||
),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: LSColors.accent.withOpacity(0.3)),
|
||||
),
|
||||
),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
cursorColor: LSColors.accent,
|
||||
textInputAction: TextInputAction.done,
|
||||
validator: validator,
|
||||
onFieldSubmitted: onSubmitted,
|
||||
);
|
||||
|
||||
static Widget tile({
|
||||
@required bool icon,
|
||||
IconData iconData,
|
||||
@required IconData icon,
|
||||
Color iconColor,
|
||||
@required String text,
|
||||
RichText subtitle,
|
||||
@required Function onTap,
|
||||
}) => ListTile(
|
||||
leading: Icon(
|
||||
iconData ?? Icons.error_outline,
|
||||
color: iconColor ?? LSColors.accent,
|
||||
leading: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
LSIcon(
|
||||
icon: icon ?? Icons.error_outline,
|
||||
color: iconColor ?? LSColors.accent,
|
||||
),
|
||||
],
|
||||
),
|
||||
title: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
subtitle: text == null
|
||||
? null
|
||||
: subtitle,
|
||||
onTap: onTap,
|
||||
contentPadding: tileContentPadding(),
|
||||
);
|
||||
|
||||
static TextSpan bolded({ @required String title, double fontSize = 14.0, Color color }) => TextSpan(
|
||||
text: title,
|
||||
style: TextStyle(
|
||||
color: color == null
|
||||
? LSColors.accent
|
||||
: color,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: fontSize,
|
||||
),
|
||||
static EdgeInsets tileContentPadding() => EdgeInsets.fromLTRB(32.0, 0.0, 16.0, 0.0);
|
||||
static EdgeInsets textDialogContentPadding() => EdgeInsets.fromLTRB(24.0, 36.0, 24.0, 14.0);
|
||||
static EdgeInsets listDialogContentPadding() => EdgeInsets.fromLTRB(0.0, 26.0, 24.0, 0.0);
|
||||
static EdgeInsets inputTextDialogContentPadding() => EdgeInsets.fromLTRB(24.0, 36.0, 24.0, 22.0);
|
||||
static EdgeInsets inputDialogContentPadding() => EdgeInsets.fromLTRB(24.0, 22.0, 24.0, 22.0);
|
||||
|
||||
static ShapeBorder shape() => RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS),
|
||||
);
|
||||
|
||||
static Future<void> dialog({
|
||||
@required BuildContext context,
|
||||
@required String title,
|
||||
@required List<Widget> content,
|
||||
@required EdgeInsets contentPadding,
|
||||
List<Widget> buttons,
|
||||
}) async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
actions: <Widget>[
|
||||
LSDialog.cancel(
|
||||
context,
|
||||
textColor: buttons != null ? Colors.white : LSColors.accent,
|
||||
),
|
||||
if(buttons != null) ...buttons,
|
||||
],
|
||||
title: LSDialog.title(text: title),
|
||||
content: LSDialog.content(children: content),
|
||||
contentPadding: contentPadding,
|
||||
shape: LSDialog.shape(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,607 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
import 'package:lunasea/modules/lidarr.dart';
|
||||
|
||||
class LSDialogLidarr {
|
||||
LSDialogLidarr._();
|
||||
|
||||
static Future<List<dynamic>> showEditQualityProfilePrompt(BuildContext context, List<LidarrQualityProfile> qualities) async {
|
||||
bool flag = false;
|
||||
LidarrQualityProfile quality;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Quality Profile',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: Container(
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: qualities.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return ListTile(
|
||||
title: Text(
|
||||
qualities[index].name,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
Icons.portrait,
|
||||
color: Constants.LIST_COLOR_ICONS[index%Constants.LIST_COLOR_ICONS.length],
|
||||
),
|
||||
onTap: () {
|
||||
quality = qualities[index];
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
);
|
||||
},
|
||||
),
|
||||
width: 400,
|
||||
),
|
||||
contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 32.0, 0.0),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag, quality];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showEditMetadataProfilePrompt(BuildContext context, List<LidarrMetadataProfile> metadatas) async {
|
||||
bool flag = false;
|
||||
LidarrMetadataProfile metadata;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Metadata Profile',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: Container(
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: metadatas.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return ListTile(
|
||||
title: Text(
|
||||
metadatas[index].name,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
Icons.portrait,
|
||||
color: Constants.LIST_COLOR_ICONS[index%Constants.LIST_COLOR_ICONS.length],
|
||||
),
|
||||
onTap: () {
|
||||
metadata = metadatas[index];
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
);
|
||||
},
|
||||
),
|
||||
width: 400,
|
||||
),
|
||||
contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 32.0, 0.0),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag, metadata];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showDeleteArtistPrompt(BuildContext context) async {
|
||||
bool flag = false;
|
||||
bool files = false;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Remove Artist',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Remove + Files',
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
flag = true;
|
||||
files = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Remove',
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
flag = true;
|
||||
files = false;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: Text(
|
||||
'Are you sure you want to remove the artist from Lidarr?',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag, files];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showDownloadWarningPrompt(BuildContext context) async {
|
||||
bool flag = false;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Download Release',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Download',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: ListBody(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'Are you sure you want to download this release? It has been marked as a rejected release by Lidarr.',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showSearchMissingPrompt(BuildContext context) async {
|
||||
bool flag = false;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Search All Missing',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Search',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: Text(
|
||||
'Are you sure you want to search for all missing albums?',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showEditArtistPrompt(BuildContext context, LidarrCatalogueData entry) async {
|
||||
bool flag = false;
|
||||
String value = '';
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
entry.title,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: ListBody(
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.edit,
|
||||
color: Colors.blue,
|
||||
),
|
||||
title: Text(
|
||||
'Edit Artist',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'edit_artist';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.refresh,
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
title: Text(
|
||||
'Refresh Artist',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'refresh_artist';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.delete,
|
||||
color: Colors.red,
|
||||
),
|
||||
title: Text(
|
||||
'Remove Artist',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'remove_artist';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
);
|
||||
},
|
||||
);
|
||||
return [flag, value];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showEditRootFolderPrompt(BuildContext context, List<LidarrRootFolder> folders) async {
|
||||
bool flag = false;
|
||||
LidarrRootFolder folder;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Root Folder',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: Container(
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: folders.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return ListTile(
|
||||
title: Text(
|
||||
folders[index].path,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
folders[index].freeSpace.lsBytes_BytesToString(),
|
||||
style: TextStyle(
|
||||
color: LSColors.accent,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
leading: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
LSIcon(
|
||||
icon: Icons.folder,
|
||||
color: Constants.LIST_COLOR_ICONS[index%Constants.LIST_COLOR_ICONS.length],
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
folder = folders[index];
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
);
|
||||
},
|
||||
),
|
||||
width: 400,
|
||||
),
|
||||
contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 32.0, 0.0),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag, folder];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showSettingsPrompt(BuildContext context) async {
|
||||
bool flag = false;
|
||||
String value = '';
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Lidarr Settings',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: ListBody(
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.language,
|
||||
color: Colors.blue,
|
||||
),
|
||||
title: Text(
|
||||
'View Web GUI',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'web_gui';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.autorenew,
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
title: Text(
|
||||
'Update Library',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'update_library';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.rss_feed,
|
||||
color: Colors.orange,
|
||||
),
|
||||
title: Text(
|
||||
'Run RSS Sync',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'rss_sync';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.search,
|
||||
color: Colors.red,
|
||||
),
|
||||
title: Text(
|
||||
'Search All Missing',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'missing_search';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.save,
|
||||
color: Colors.deepPurpleAccent,
|
||||
),
|
||||
title: Text(
|
||||
'Backup Database',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'backup';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
);
|
||||
},
|
||||
);
|
||||
return [flag, value];
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,661 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
import 'package:lunasea/modules/radarr.dart';
|
||||
|
||||
class LSDialogRadarr {
|
||||
LSDialogRadarr._();
|
||||
|
||||
static Future<List<dynamic>> showDeleteMovieFilePrompt(BuildContext context) async {
|
||||
bool flag = false;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Delete Movie File',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Delete',
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: Text(
|
||||
'Are you sure you want to delete this movie file?',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showDeleteMoviePrompt(BuildContext context) async {
|
||||
bool flag = false;
|
||||
bool files = false;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Remove Movie',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Remove + Files',
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
flag = true;
|
||||
files = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Remove',
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
flag = true;
|
||||
files = false;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: Text(
|
||||
'Are you sure you want to remove the movie from Radarr?',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag, files];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showSearchMissingPrompt(BuildContext context) async {
|
||||
bool flag = false;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Search For All Movies',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Search',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: Text(
|
||||
'Are you sure you want to search for all missing movies?',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showEditMoviePrompt(BuildContext context, RadarrCatalogueData entry) async {
|
||||
bool flag = false;
|
||||
String value = '';
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
entry.title,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: ListBody(
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.edit,
|
||||
color: Colors.blue,
|
||||
),
|
||||
title: Text(
|
||||
'Edit Movie',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'edit_movie';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.refresh,
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
title: Text(
|
||||
'Refresh Movie',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'refresh_movie';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.delete,
|
||||
color: Colors.red,
|
||||
),
|
||||
title: Text(
|
||||
'Remove Movie',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'remove_movie';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
);
|
||||
},
|
||||
);
|
||||
return [flag, value];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showMinimumAvailabilityPrompt(BuildContext context, List<RadarrAvailability> availability) async {
|
||||
bool flag = false;
|
||||
RadarrAvailability entry;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Minimum Availability',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: Container(
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: availability.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return ListTile(
|
||||
title: Text(
|
||||
availability[index].name,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
Icons.folder,
|
||||
color: Constants.LIST_COLOR_ICONS[index%Constants.LIST_COLOR_ICONS.length],
|
||||
),
|
||||
onTap: () {
|
||||
entry = availability[index];
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
);
|
||||
},
|
||||
),
|
||||
width: 400,
|
||||
),
|
||||
contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 32.0, 0.0),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag, entry];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showEditRootFolderPrompt(BuildContext context, List<RadarrRootFolder> folders) async {
|
||||
bool flag = false;
|
||||
RadarrRootFolder folder;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Root Folder',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: Container(
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: folders.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return ListTile(
|
||||
title: Text(
|
||||
folders[index].path,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
folders[index].freeSpace.lsBytes_BytesToString(),
|
||||
style: TextStyle(
|
||||
color: LSColors.accent,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
leading: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
LSIcon(
|
||||
icon: Icons.folder,
|
||||
color: Constants.LIST_COLOR_ICONS[index%Constants.LIST_COLOR_ICONS.length],
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
folder = folders[index];
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
);
|
||||
},
|
||||
),
|
||||
width: 400,
|
||||
),
|
||||
contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 32.0, 0.0),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag, folder];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showEditQualityProfilePrompt(BuildContext context, List<RadarrQualityProfile> qualities) async {
|
||||
bool flag = false;
|
||||
RadarrQualityProfile quality;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Quality Profile',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: Container(
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: qualities.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return ListTile(
|
||||
title: Text(
|
||||
qualities[index].name,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
Icons.portrait,
|
||||
color: Constants.LIST_COLOR_ICONS[index%Constants.LIST_COLOR_ICONS.length],
|
||||
),
|
||||
onTap: () {
|
||||
quality = qualities[index];
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
);
|
||||
},
|
||||
),
|
||||
width: 400,
|
||||
),
|
||||
contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 32.0, 0.0),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag, quality];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showDownloadWarningPrompt(BuildContext context) async {
|
||||
bool flag = false;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Download Release',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Download',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: ListBody(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'Are you sure you want to download this release? It has been marked as a rejected release by Radarr.',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showSettingsPrompt(BuildContext context) async {
|
||||
bool flag = false;
|
||||
String value = '';
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Radarr Settings',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: ListBody(
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.language,
|
||||
color: Colors.blue,
|
||||
),
|
||||
title: Text(
|
||||
'View Web GUI',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'web_gui';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.autorenew,
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
title: Text(
|
||||
'Update Library',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'update_library';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.rss_feed,
|
||||
color: Colors.orange,
|
||||
),
|
||||
title: Text(
|
||||
'Run RSS Sync',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'rss_sync';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.search,
|
||||
color: Colors.red,
|
||||
),
|
||||
title: Text(
|
||||
'Search All Missing',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'missing_search';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.save,
|
||||
color: Colors.deepPurpleAccent,
|
||||
),
|
||||
title: Text(
|
||||
'Backup Database',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'backup';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
);
|
||||
},
|
||||
);
|
||||
return [flag, value];
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,561 +0,0 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
import 'package:lunasea/modules.dart';
|
||||
|
||||
class LSDialogSettings {
|
||||
LSDialogSettings._();
|
||||
|
||||
static Future<List> deleteIndexer(BuildContext context) async {
|
||||
//Returns
|
||||
bool _flag = false;
|
||||
//Dialog
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: LSDialog.title(text: 'Delete Indexer'),
|
||||
actions: <Widget>[
|
||||
LSDialog.cancel(context),
|
||||
LSDialog.button(
|
||||
text: 'Delete',
|
||||
onPressed: () {
|
||||
_flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
textColor: Colors.red,
|
||||
),
|
||||
],
|
||||
content: LSDialog.content(
|
||||
children: [
|
||||
LSDialog.textContent(
|
||||
text: 'Are you sure you want to delete this indexer?'
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
return [_flag];
|
||||
}
|
||||
|
||||
static Future<void> showChangelog(BuildContext context, List changes) async {
|
||||
const _headerStyle = TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
fontWeight: FontWeight.bold,
|
||||
);
|
||||
//Dialog
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: LSDialog.title(text: 'Changelog'),
|
||||
actions: <Widget>[
|
||||
LSDialog.button(
|
||||
text: 'Close',
|
||||
textColor: LSColors.accent,
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
],
|
||||
content: LSDialog.content(
|
||||
children: List.generate(
|
||||
changes.length,
|
||||
(index) => ListTile(
|
||||
title: LSTitle(
|
||||
text: '${changes[index]['version']}',
|
||||
maxLines: 2,
|
||||
),
|
||||
subtitle: Padding(
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
style: TextStyle(color: Colors.white70),
|
||||
children: <TextSpan>[
|
||||
TextSpan(text: 'New\n', style: _headerStyle),
|
||||
LSDialog.textSpanContent(text: changes[index]['new'].length == 0 ? 'No Changes' : '- ${changes[index]['new'].join('\n- ')}'),
|
||||
TextSpan(text: '\n\nFixes\n', style: _headerStyle),
|
||||
LSDialog.textSpanContent(text: changes[index]['fixes'].length == 0 ? 'No Changes' : '- ${changes[index]['fixes'].join('\n- ')}'),
|
||||
TextSpan(text: '\n\nTweaks\n', style: _headerStyle),
|
||||
LSDialog.textSpanContent(text: changes[index]['tweaks'].length == 0 ? 'No Changes' : '- ${changes[index]['tweaks'].join('\n- ')}'),
|
||||
]
|
||||
),
|
||||
),
|
||||
padding: EdgeInsets.symmetric(vertical: 10.0),
|
||||
),
|
||||
),
|
||||
).toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<List> exportLogs(BuildContext context) async {
|
||||
//Returns
|
||||
bool _flag = false;
|
||||
//Setter
|
||||
void _setValues(bool flag) {
|
||||
_flag = flag;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
//Dialog
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: LSDialog.title(text: 'Export Logs'),
|
||||
actions: <Widget>[
|
||||
LSDialog.cancel(context),
|
||||
LSDialog.button(
|
||||
text: 'Export',
|
||||
onPressed: () => _setValues(true),
|
||||
textColor: LSColors.accent,
|
||||
),
|
||||
],
|
||||
content: LSDialog.content(
|
||||
children: [
|
||||
LSDialog.textContent(text: 'Are you sure you want to export all recorded logs to the filesystem?\n'),
|
||||
RichText(
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
children: [
|
||||
if(Platform.isIOS || Platform.isAndroid) LSDialog.textSpanContent(text: 'The exported logs can be found in '),
|
||||
if(Platform.isIOS) LSDialog.bolded(title: '<On My Device>/LunaSea/FLogs', fontSize: 16.0),
|
||||
if(Platform.isAndroid) LSDialog.bolded(title: '<Storage>/Android/data/app.lunasea.lunasea/files/FLogs', fontSize: 16.0),
|
||||
LSDialog.textSpanContent(text: '.'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
return [_flag];
|
||||
}
|
||||
|
||||
static Future<List> clearLogs(BuildContext context) async {
|
||||
//Returns
|
||||
bool _flag = false;
|
||||
//Setter
|
||||
void _setValues(bool flag) {
|
||||
_flag = flag;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
//Dialog
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: LSDialog.title(text: 'Clear Logs'),
|
||||
actions: <Widget>[
|
||||
LSDialog.cancel(context),
|
||||
LSDialog.button(
|
||||
text: 'Clear',
|
||||
onPressed: () => _setValues(true),
|
||||
textColor: LSColors.red,
|
||||
),
|
||||
],
|
||||
content: LSDialog.content(
|
||||
children: [
|
||||
LSDialog.textContent(text: 'Are you sure you want to clear all recorded logs?\n\nLogs can be useful for bug reports and debugging.'),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
return [_flag];
|
||||
}
|
||||
|
||||
static Future<List> toggleStrictTLS(BuildContext context) async {
|
||||
//Returns
|
||||
bool _flag = false;
|
||||
//Setter
|
||||
void _setValues(bool flag) {
|
||||
_flag = flag;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
//Dialog
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: LSDialog.title(text: 'Disable Strict SSL/TLS Validation'),
|
||||
actions: <Widget>[
|
||||
LSDialog.cancel(context),
|
||||
LSDialog.button(
|
||||
text: 'Disable',
|
||||
onPressed: () => _setValues(true),
|
||||
textColor: LSColors.red,
|
||||
),
|
||||
],
|
||||
content: RichText(
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
children: [
|
||||
LSDialog.bolded(
|
||||
title: 'Please do not modify this setting unless you know what you are doing.\n\n',
|
||||
color: LSColors.red,
|
||||
fontSize: 12.0,
|
||||
),
|
||||
LSDialog.textSpanContent(text: 'Are you sure you want to disable strict SSL/TLS validation?\n\n'),
|
||||
LSDialog.textSpanContent(text: 'Disabling strict SSL/TLS validation means that LunaSea will not validate the host machine\'s SSL certificate against a certificate authority.\n\n'),
|
||||
LSDialog.textSpanContent(text: 'LunaSea will still connect to your host machine securely when using SSL/TLS whether strict SSL/TLS validation is enabled or disabled.\n\n'),
|
||||
LSDialog.bolded(
|
||||
title: 'Warning: Disabling strict SSL/TLS for an invalid or self-signed certificate will prevent a large amount of images from loading within LunaSea.',
|
||||
color: LSColors.red,
|
||||
fontSize: 12.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
return [_flag];
|
||||
}
|
||||
|
||||
static Future<List> nzbgetBasicAuthentication(BuildContext context) async {
|
||||
//Returns
|
||||
bool _flag = false;
|
||||
//Setter
|
||||
void _setValues(bool flag) {
|
||||
_flag = flag;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
//Dialog
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: LSDialog.title(text: 'Use Basic Authentication'),
|
||||
actions: <Widget>[
|
||||
LSDialog.cancel(context),
|
||||
LSDialog.button(
|
||||
text: 'Use',
|
||||
onPressed: () => _setValues(true),
|
||||
textColor: LSColors.red,
|
||||
),
|
||||
],
|
||||
content: RichText(
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
children: [
|
||||
LSDialog.bolded(
|
||||
title: 'Please do not modify this setting unless you know what you are doing.\n\n',
|
||||
color: LSColors.red,
|
||||
fontSize: 12.0,
|
||||
),
|
||||
LSDialog.textSpanContent(text: 'Are you sure you want to use basic authentication?\n\n'),
|
||||
LSDialog.textSpanContent(text: 'Basic authentication will add your username and password as a header in the request instead of in the URL.\n\n'),
|
||||
LSDialog.bolded(
|
||||
title: 'Warning: This will allow you to have more complex passwords, but interfere with layered authentication methods.',
|
||||
color: LSColors.red,
|
||||
fontSize: 12.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
return [_flag];
|
||||
}
|
||||
|
||||
static Future<List> addProfile(BuildContext context) async {
|
||||
//Returns
|
||||
TextEditingController _controller = TextEditingController();
|
||||
bool _flag = false;
|
||||
//Setter
|
||||
void _setValues(bool flag) {
|
||||
_flag = flag;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
//Dialog
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: LSDialog.title(text: 'Add Profile'),
|
||||
actions: <Widget>[
|
||||
LSDialog.cancel(context),
|
||||
LSDialog.button(
|
||||
text: 'Save',
|
||||
onPressed: () => _setValues(true),
|
||||
textColor: LSColors.accent,
|
||||
),
|
||||
],
|
||||
content: LSDialog.content(
|
||||
children: [
|
||||
LSDialog.textInput(
|
||||
controller: _controller,
|
||||
onSubmitted: (_) => _setValues(true),
|
||||
title: 'Profile Name',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
return [_flag, _controller.text];
|
||||
}
|
||||
|
||||
static Future<List> renameProfile(BuildContext context, List<String> profiles) async {
|
||||
//Returns
|
||||
bool _flag = false;
|
||||
String _profile = '';
|
||||
//Setter
|
||||
void _setValues(bool flag, String profile) {
|
||||
_flag = flag;
|
||||
_profile = profile;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
//Dialog
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: LSDialog.title(text: 'Add Profile'),
|
||||
actions: <Widget>[
|
||||
LSDialog.cancel(context, textColor: LSColors.accent),
|
||||
],
|
||||
content: LSDialog.content(
|
||||
children: List.generate(
|
||||
profiles.length,
|
||||
(index) => ListTile(
|
||||
leading: LSIcon(
|
||||
icon: Icons.settings,
|
||||
color: LSColors.list(index),
|
||||
),
|
||||
title: Text(
|
||||
profiles[index],
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
onTap: () => _setValues(true, profiles[index]),
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
contentPadding: EdgeInsets.only(left: 24.0, top: 20.0, right: 24.0),
|
||||
),
|
||||
);
|
||||
return [_flag, _profile];
|
||||
}
|
||||
|
||||
static Future<List> renameProfileSelected(BuildContext context) async {
|
||||
//Returns
|
||||
final _controller = TextEditingController();
|
||||
bool _flag = false;
|
||||
//Setters
|
||||
void _setValues(bool flag) {
|
||||
_flag = flag;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) => AlertDialog(
|
||||
title: LSDialog.title(text: 'Rename Profile'),
|
||||
actions: <Widget>[
|
||||
LSDialog.cancel(context),
|
||||
LSDialog.button(text: 'Rename', onPressed: () => _setValues(true), textColor: LSColors.accent),
|
||||
],
|
||||
content: LSDialog.content(
|
||||
children: [
|
||||
LSDialog.textInput(
|
||||
controller: _controller,
|
||||
onSubmitted: (_) => _setValues(true),
|
||||
title: 'New Profile Name',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
return [_flag, _controller.text];
|
||||
}
|
||||
|
||||
static Future<List> deleteProfile(BuildContext context, List<String> profiles) async {
|
||||
//Returns
|
||||
bool _flag = false;
|
||||
String _profile = '';
|
||||
//Setter
|
||||
void _setValues(bool flag, String profile) {
|
||||
_flag = flag;
|
||||
_profile = profile;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
//Dialog
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: LSDialog.title(text: 'Delete Profile'),
|
||||
actions: <Widget>[
|
||||
LSDialog.cancel(context, textColor: LSColors.accent),
|
||||
],
|
||||
content: LSDialog.content(
|
||||
children: List.generate(
|
||||
profiles.length,
|
||||
(index) => ListTile(
|
||||
leading: LSIcon(
|
||||
icon: Icons.settings,
|
||||
color: LSColors.list(index),
|
||||
),
|
||||
title: Text(
|
||||
profiles[index],
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
onTap: () => _setValues(true, profiles[index]),
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
contentPadding: EdgeInsets.only(left: 24.0, top: 20.0, right: 24.0),
|
||||
),
|
||||
);
|
||||
return [_flag, _profile];
|
||||
}
|
||||
|
||||
static Future<List> changeProfile(BuildContext context, List<String> profiles) async {
|
||||
//Returns
|
||||
bool _flag = false;
|
||||
String _profile = '';
|
||||
//Setter
|
||||
void _setValues(bool flag, String profile) {
|
||||
_flag = flag;
|
||||
_profile = profile;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
//Dialog
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: LSDialog.title(text: 'Enabled Profile'),
|
||||
actions: <Widget>[
|
||||
LSDialog.cancel(context, textColor: LSColors.accent),
|
||||
],
|
||||
content: LSDialog.content(
|
||||
children: List.generate(
|
||||
profiles.length,
|
||||
(index) => ListTile(
|
||||
leading: LSIcon(
|
||||
icon: Icons.settings,
|
||||
color: LSColors.list(index),
|
||||
),
|
||||
title: Text(
|
||||
profiles[index],
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
onTap: () => _setValues(true, profiles[index]),
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
contentPadding: EdgeInsets.only(left: 24.0, top: 20.0, right: 24.0),
|
||||
),
|
||||
);
|
||||
return [_flag, _profile];
|
||||
}
|
||||
|
||||
static Future<List> changeBrowser(BuildContext context) async {
|
||||
//Returns
|
||||
bool _flag = false;
|
||||
LSBrowsers _browser;
|
||||
//Setter
|
||||
void _setValues(bool flag, LSBrowsers browser) {
|
||||
_flag = flag;
|
||||
_browser = browser;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: LSDialog.title(text: 'Open Links In...'),
|
||||
actions: <Widget>[
|
||||
LSDialog.cancel(context, textColor: LSColors.accent),
|
||||
],
|
||||
content: LSDialog.content(
|
||||
children: List.generate(
|
||||
LSBrowsers.values.length,
|
||||
(index) => ListTile(
|
||||
leading: LSIcon(
|
||||
icon: LSBrowsers.values[index].icon,
|
||||
color: LSColors.list(index),
|
||||
),
|
||||
title: Text(
|
||||
LSBrowsers.values[index].name,
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
onTap: () => _setValues(true, LSBrowsers.values[index]),
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
contentPadding: EdgeInsets.only(left: 24.0, top: 20.0, right: 24.0),
|
||||
),
|
||||
);
|
||||
return [_flag, _browser];
|
||||
}
|
||||
|
||||
static Future<List> homeChangeStartingDay(BuildContext context) async {
|
||||
//Returns
|
||||
bool _flag = false;
|
||||
CalendarStartingDay _startingDate;
|
||||
//Setter
|
||||
void _setValues(bool flag, CalendarStartingDay startingDate) {
|
||||
_flag = flag;
|
||||
_startingDate = startingDate;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: LSDialog.title(text: 'Starting Day of Week'),
|
||||
actions: <Widget>[
|
||||
LSDialog.cancel(context, textColor: LSColors.accent),
|
||||
],
|
||||
content: LSDialog.content(
|
||||
children: List.generate(
|
||||
CalendarStartingDay.values.length,
|
||||
(index) => ListTile(
|
||||
leading: LSIcon(
|
||||
icon: CustomIcons.calendar,
|
||||
color: LSColors.list(index),
|
||||
),
|
||||
title: Text(
|
||||
CalendarStartingDay.values[index].name,
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
onTap: () => _setValues(true, CalendarStartingDay.values[index]),
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
contentPadding: EdgeInsets.only(left: 24.0, top: 20.0, right: 24.0),
|
||||
),
|
||||
);
|
||||
return [_flag, _startingDate];
|
||||
}
|
||||
|
||||
static Future<List> homeChangeStartingSize(BuildContext context) async {
|
||||
//Returns
|
||||
bool _flag = false;
|
||||
CalendarStartingSize _startingSize;
|
||||
//Setter
|
||||
void _setValues(bool flag, CalendarStartingSize startingSize) {
|
||||
_flag = flag;
|
||||
_startingSize = startingSize;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: LSDialog.title(text: 'Starting Size'),
|
||||
actions: <Widget>[
|
||||
LSDialog.cancel(context, textColor: LSColors.accent),
|
||||
],
|
||||
content: LSDialog.content(
|
||||
children: List.generate(
|
||||
CalendarStartingSize.values.length,
|
||||
(index) => ListTile(
|
||||
leading: LSIcon(
|
||||
icon: CalendarStartingSize.values[index].icon,
|
||||
color: LSColors.list(index),
|
||||
),
|
||||
title: Text(
|
||||
CalendarStartingSize.values[index].name,
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
onTap: () => _setValues(true, CalendarStartingSize.values[index]),
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
contentPadding: EdgeInsets.only(left: 24.0, top: 20.0, right: 24.0),
|
||||
),
|
||||
);
|
||||
return [_flag, _startingSize];
|
||||
}
|
||||
}
|
||||
@@ -1,831 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:lunasea/modules/sonarr.dart';
|
||||
|
||||
class LSDialogSonarr {
|
||||
LSDialogSonarr._();
|
||||
|
||||
static Future<List<dynamic>> showDownloadWarningPrompt(BuildContext context) async {
|
||||
bool flag = false;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Download Release',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Download',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: ListBody(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'Are you sure you want to download this release? It has been marked as a rejected release by Sonarr.',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showDeleteSeriesPrompt(BuildContext context) async {
|
||||
bool flag = false;
|
||||
bool files = false;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Remove Series',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Remove + Files',
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
flag = true;
|
||||
files = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Remove',
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
flag = true;
|
||||
files = false;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: Text(
|
||||
'Are you sure you want to remove the series from Sonarr?',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag, files];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showSearchMissingPrompt(BuildContext context) async {
|
||||
bool flag = false;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Search All Missing',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Search',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: Text(
|
||||
'Are you sure you want to search for all missing episodes?',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showSettingsPrompt(BuildContext context) async {
|
||||
bool flag = false;
|
||||
String value = '';
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Sonarr Settings',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: ListBody(
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.language,
|
||||
color: Colors.blue,
|
||||
),
|
||||
title: Text(
|
||||
'View Web GUI',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'web_gui';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.autorenew,
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
title: Text(
|
||||
'Update Library',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'update_library';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.rss_feed,
|
||||
color: Colors.orange,
|
||||
),
|
||||
title: Text(
|
||||
'Run RSS Sync',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'rss_sync';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.search,
|
||||
color: Colors.red,
|
||||
),
|
||||
title: Text(
|
||||
'Search All Missing',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'missing_search';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.save,
|
||||
color: Colors.deepPurpleAccent,
|
||||
),
|
||||
title: Text(
|
||||
'Backup Database',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'backup';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
);
|
||||
},
|
||||
);
|
||||
return [flag, value];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showEpisodeEditingPrompt(BuildContext context, String title, bool monitored, bool canDelete) async {
|
||||
bool flag = false;
|
||||
String value = '';
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
title,
|
||||
maxLines: 1,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: ListBody(
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
monitored ? Icons.turned_in_not : Icons.turned_in,
|
||||
color: Colors.blue,
|
||||
),
|
||||
title: Text(
|
||||
monitored ? 'Unmonitor Episode' : 'Monitor Episode',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'monitor_status';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.search,
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
title: Text(
|
||||
'Automatic Search',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'search_automatic';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.youtube_searched_for,
|
||||
color: Colors.orange,
|
||||
),
|
||||
title: Text(
|
||||
'Manual Search',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'search_manual';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
if(canDelete) ListTile(
|
||||
leading: Icon(
|
||||
Icons.delete,
|
||||
color: Colors.red,
|
||||
),
|
||||
title: Text(
|
||||
'Delete File',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'delete_file';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
);
|
||||
},
|
||||
);
|
||||
return [flag, value];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showEditSeriesPrompt(BuildContext context, SonarrCatalogueData entry) async {
|
||||
bool flag = false;
|
||||
String value = '';
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
entry.title,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: ListBody(
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.edit,
|
||||
color: Colors.blue,
|
||||
),
|
||||
title: Text(
|
||||
'Edit Series',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'edit_series';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.refresh,
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
title: Text(
|
||||
'Refresh Series',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'refresh_series';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.delete,
|
||||
color: Colors.red,
|
||||
),
|
||||
title: Text(
|
||||
'Remove Series',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
value = 'remove_series';
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
);
|
||||
},
|
||||
);
|
||||
return [flag, value];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showEditRootFolderPrompt(BuildContext context, List<SonarrRootFolder> folders) async {
|
||||
bool flag = false;
|
||||
SonarrRootFolder folder;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Root Folder',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: Container(
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: folders.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return ListTile(
|
||||
title: Text(
|
||||
folders[index].path,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
folders[index].freeSpace.lsBytes_BytesToString(),
|
||||
style: TextStyle(
|
||||
color: LSColors.accent,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
leading: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
LSIcon(
|
||||
icon: Icons.folder,
|
||||
color: Constants.LIST_COLOR_ICONS[index%Constants.LIST_COLOR_ICONS.length],
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
folder = folders[index];
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
);
|
||||
},
|
||||
),
|
||||
width: 400,
|
||||
),
|
||||
contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 32.0, 0.0),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag, folder];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showEditQualityProfilePrompt(BuildContext context, List<SonarrQualityProfile> qualities) async {
|
||||
bool flag = false;
|
||||
SonarrQualityProfile quality;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Quality Profile',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: Container(
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: qualities.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return ListTile(
|
||||
title: Text(
|
||||
qualities[index].name,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
Icons.portrait,
|
||||
color: Constants.LIST_COLOR_ICONS[index%Constants.LIST_COLOR_ICONS.length],
|
||||
),
|
||||
onTap: () {
|
||||
quality = qualities[index];
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
);
|
||||
},
|
||||
),
|
||||
width: 400,
|
||||
),
|
||||
contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 32.0, 0.0),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag, quality];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showEditSeriesTypePrompt(BuildContext context) async {
|
||||
bool flag = false;
|
||||
SonarrSeriesType type;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Series Type',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: Container(
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: SonarrConstants.SERIES_TYPES.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return ListTile(
|
||||
title: Text(
|
||||
toBeginningOfSentenceCase(SonarrConstants.SERIES_TYPES[index].type),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
Icons.tab,
|
||||
color: Constants.LIST_COLOR_ICONS[index%Constants.LIST_COLOR_ICONS.length],
|
||||
),
|
||||
onTap: () {
|
||||
type = SonarrConstants.SERIES_TYPES[index];
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
);
|
||||
},
|
||||
),
|
||||
width: 400,
|
||||
),
|
||||
contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 32.0, 0.0),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag, type];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showSearchSeasonPrompt(BuildContext context, int seasonNumber) async {
|
||||
bool flag = false;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Episode Search',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Search',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: Text(
|
||||
seasonNumber == 0 ? 'Search for all episodes in specials?' : 'Search for all episodes in season $seasonNumber?',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showDeleteFilePrompt(BuildContext context) async {
|
||||
bool flag = false;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Delete Episode File',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Delete',
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: Text(
|
||||
'Are you sure you want to delete this episode file?',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag];
|
||||
}
|
||||
}
|
||||
@@ -1,435 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
|
||||
class LSDialogSystem {
|
||||
LSDialogSystem._();
|
||||
|
||||
static Future<List> editText(BuildContext context, String title, {String prefill = '', bool showHostHint = false}) async {
|
||||
//Returns
|
||||
bool _flag = false;
|
||||
TextEditingController _controller = TextEditingController()..text = prefill;
|
||||
//Setter
|
||||
void _setValues(bool flag) {
|
||||
_flag = flag;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
//Dialog
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: LSDialog.title(text: title),
|
||||
actions: <Widget>[
|
||||
LSDialog.cancel(context),
|
||||
LSDialog.button(
|
||||
text: 'Save',
|
||||
textColor: LSColors.accent,
|
||||
onPressed: () => _setValues(true)),
|
||||
],
|
||||
content: LSDialog.content(
|
||||
children: [
|
||||
if(showHostHint) RichText(
|
||||
text: TextSpan(
|
||||
style: TextStyle(color: Colors.white70),
|
||||
children: [
|
||||
TextSpan(text: '•\tThis is the URL in which you access the web GUI for the service\n'),
|
||||
TextSpan(text: '•\tYou must include either '),
|
||||
LSDialog.bolded(title: 'http://'),
|
||||
TextSpan(text: ' or '),
|
||||
LSDialog.bolded(title: 'https://\n'),
|
||||
TextSpan(text: '•\tWhen not using a reverse proxy, please include the port: '),
|
||||
LSDialog.bolded(title: 'url:port\n'),
|
||||
TextSpan(text: '•\tTo add layered authentication (Nginx, Apache, etc), use the following format: '),
|
||||
LSDialog.bolded(title: 'http(s)://user:pass@hostname\n'),
|
||||
],
|
||||
),
|
||||
),
|
||||
LSDialog.textInput(
|
||||
controller: _controller,
|
||||
onSubmitted: (_) => _setValues(true),
|
||||
title: title,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
return [_flag, _controller.text];
|
||||
}
|
||||
|
||||
static Future<void> textPreview(BuildContext context, String title, String text, {bool alignLeft = false}) async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: LSDialog.title(text: title),
|
||||
actions: <Widget>[
|
||||
LSDialog.button(
|
||||
text: 'Close',
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
textColor: LSColors.accent,
|
||||
),
|
||||
],
|
||||
content: LSDialog.content(
|
||||
children: [
|
||||
LSDialog.textContent(
|
||||
text: text,
|
||||
textAlign: alignLeft
|
||||
? TextAlign.left
|
||||
: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> deleteCatalogueWithFiles(BuildContext context, String title) async {
|
||||
//Returns
|
||||
bool _flag = false;
|
||||
//Setter
|
||||
void _setValues(bool flag) {
|
||||
_flag = flag;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: LSDialog.title(text: title),
|
||||
actions: <Widget>[
|
||||
LSDialog.cancel(context),
|
||||
LSDialog.button(
|
||||
text: 'Delete',
|
||||
textColor: LSColors.red,
|
||||
onPressed: () => _setValues(true),
|
||||
),
|
||||
],
|
||||
content: LSDialog.content(
|
||||
children: [
|
||||
LSDialog.textContent(text: 'Are you sure you want to delete all the files and folders for $title?'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [_flag];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showBackupConfigurationPrompt(BuildContext context) async {
|
||||
bool flag = false;
|
||||
final formKey = GlobalKey<FormState>();
|
||||
final textController = TextEditingController();
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Backup Configuration',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Backup',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
if(formKey.currentState.validate()) {
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
content: LSDialog.content(
|
||||
children: [
|
||||
LSDialog.textContent(text: 'Are you sure you want to backup your current configuration?\n'),
|
||||
LSDialog.textContent(text: 'All backups are encrypted before being exported to the filesystem, please enter an encryption key for the backup.\n'),
|
||||
RichText(
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
children: [
|
||||
if(Platform.isIOS || Platform.isAndroid) LSDialog.textSpanContent(text: 'The backups can be found in '),
|
||||
if(Platform.isIOS) LSDialog.bolded(title: '<On My Device>/LunaSea/configurations', fontSize: 16.0),
|
||||
if(Platform.isAndroid) LSDialog.bolded(title: '<Storage>/Android/data/app.lunasea.lunasea/files/configurations', fontSize: 16.0),
|
||||
LSDialog.textSpanContent(text: '.'),
|
||||
],
|
||||
),
|
||||
),
|
||||
Form(
|
||||
key: formKey,
|
||||
child: TextFormField(
|
||||
autofocus: true,
|
||||
autocorrect: false,
|
||||
obscureText: true,
|
||||
controller: textController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Encryption Key',
|
||||
labelStyle: TextStyle(
|
||||
color: Colors.white54,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
cursorColor: Color(Constants.ACCENT_COLOR),
|
||||
validator: (value) {
|
||||
if(value.length < 8) {
|
||||
return 'Minimum of 8 characters';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag, textController.text];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showRestoreConfigurationPrompt(BuildContext context, List<String> paths) async {
|
||||
bool flag = false;
|
||||
String path = '';
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Restore Configuration',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: Container(
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: paths.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return ListTile(
|
||||
leading: Icon(
|
||||
Icons.cloud_download,
|
||||
color: Constants.LIST_COLOR_ICONS[index%Constants.LIST_COLOR_ICONS.length],
|
||||
),
|
||||
title: Text(
|
||||
paths[(paths.length-index-1)].substring(
|
||||
paths[(paths.length-index-1)].indexOf('/configurations/')+16,
|
||||
paths[(paths.length-index-1)].indexOf('.json')
|
||||
).replaceAll('%', '\n'),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () async {
|
||||
path = paths[paths.length-index-1];
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
);
|
||||
},
|
||||
),
|
||||
width: 400,
|
||||
),
|
||||
contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
);
|
||||
},
|
||||
);
|
||||
return [flag, path];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showClearConfigurationPrompt(BuildContext context) async {
|
||||
bool flag = false;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Reset LunaSea',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Reset',
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: Text(
|
||||
'Are you sure you want to reset LunaSea and clear your configuration?\n\nYou will be starting from a clean slate, please ensure you backup your current configuration first!',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showEncryptionKeyPrompt(BuildContext context) async {
|
||||
bool flag = false;
|
||||
final formKey = GlobalKey<FormState>();
|
||||
final textController = TextEditingController();
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Decrypt Backup',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Restore',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
if(formKey.currentState.validate()) {
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: ListBody(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'Please enter the encryption key used for this backup.',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Form(
|
||||
key: formKey,
|
||||
child: TextFormField(
|
||||
autofocus: true,
|
||||
autocorrect: false,
|
||||
obscureText: true,
|
||||
controller: textController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Encryption Key',
|
||||
labelStyle: TextStyle(
|
||||
color: Colors.white54,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
cursorColor: Color(Constants.ACCENT_COLOR),
|
||||
validator: (value) {
|
||||
if(value.length < 8) {
|
||||
return 'Minimum of 8 characters';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag, textController.text];
|
||||
}
|
||||
}
|
||||
@@ -3,5 +3,3 @@ export './navigation/appbar_dropdown.dart';
|
||||
export './navigation/appbar_tabs.dart';
|
||||
export './navigation/drawer.dart';
|
||||
export './navigation/bottom_navigation_bar.dart';
|
||||
export './navigation/sliver_appbar.dart';
|
||||
export './navigation/sliver_appbar_tabs.dart';
|
||||
|
||||
@@ -5,7 +5,10 @@ Widget LSAppBar({
|
||||
@required String title,
|
||||
List<Widget> actions
|
||||
}) => AppBar(
|
||||
title: Text(title),
|
||||
title: Text(
|
||||
title,
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
centerTitle: false,
|
||||
elevation: 0,
|
||||
actions: actions,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
import 'package:lunasea/modules/wake_on_lan.dart';
|
||||
|
||||
class LSDrawer extends StatelessWidget {
|
||||
final String page;
|
||||
@@ -83,6 +84,7 @@ class LSDrawer extends StatelessWidget {
|
||||
title: 'Search',
|
||||
route: '/search',
|
||||
),
|
||||
if(ModuleFlags.WAKE_ON_LAN && profile.getWakeOnLAN()['enabled']) _buildWakeOnLAN(context: context),
|
||||
if(ModuleFlags.AUTOMATION && profile.anyAutomationEnabled) ExpansionTile(
|
||||
leading: Icon(CustomIcons.layers),
|
||||
title: Text('Automation'),
|
||||
@@ -91,9 +93,9 @@ class LSDrawer extends StatelessWidget {
|
||||
Database.currentProfileObject.enabledAutomationServices.length,
|
||||
(index) => _buildEntry(
|
||||
context: context,
|
||||
route: Constants.SERVICE_MAP[Database.currentProfileObject.enabledAutomationServices[index]]['route'],
|
||||
icon: Constants.SERVICE_MAP[Database.currentProfileObject.enabledAutomationServices[index]]['icon'],
|
||||
title: Constants.SERVICE_MAP[Database.currentProfileObject.enabledAutomationServices[index]]['name'],
|
||||
route: Constants.MODULE_MAP[Database.currentProfileObject.enabledAutomationServices[index]]['route'],
|
||||
icon: Constants.MODULE_MAP[Database.currentProfileObject.enabledAutomationServices[index]]['icon'],
|
||||
title: Constants.MODULE_MAP[Database.currentProfileObject.enabledAutomationServices[index]]['name'],
|
||||
padLeft: true,
|
||||
),
|
||||
),
|
||||
@@ -106,9 +108,9 @@ class LSDrawer extends StatelessWidget {
|
||||
Database.currentProfileObject.enabledClientServices.length,
|
||||
(index) => _buildEntry(
|
||||
context: context,
|
||||
route: Constants.SERVICE_MAP[Database.currentProfileObject.enabledClientServices[index]]['route'],
|
||||
icon: Constants.SERVICE_MAP[Database.currentProfileObject.enabledClientServices[index]]['icon'],
|
||||
title: Constants.SERVICE_MAP[Database.currentProfileObject.enabledClientServices[index]]['name'],
|
||||
route: Constants.MODULE_MAP[Database.currentProfileObject.enabledClientServices[index]]['route'],
|
||||
icon: Constants.MODULE_MAP[Database.currentProfileObject.enabledClientServices[index]]['icon'],
|
||||
title: Constants.MODULE_MAP[Database.currentProfileObject.enabledClientServices[index]]['name'],
|
||||
padLeft: true,
|
||||
),
|
||||
),
|
||||
@@ -149,4 +151,33 @@ class LSDrawer extends StatelessWidget {
|
||||
: EdgeInsets.fromLTRB(16.0, 0.0, 0.0, 0.0),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildWakeOnLAN({
|
||||
@required BuildContext context,
|
||||
}) {
|
||||
return ListTile(
|
||||
leading: LSIcon(icon: Constants.MODULE_MAP['wake_on_lan']['icon']),
|
||||
title: Text(
|
||||
Constants.MODULE_MAP['wake_on_lan']['name'],
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
onTap: () async {
|
||||
WakeOnLANAPI _api = WakeOnLANAPI.from(Database.currentProfileObject);
|
||||
await _api.wake()
|
||||
.then((_) => LSSnackBar(
|
||||
context: context,
|
||||
title: 'Machine is Waking Up...',
|
||||
message: 'Magic packet successfully sent',
|
||||
type: SNACKBAR_TYPE.success,
|
||||
))
|
||||
.catchError((_) => LSSnackBar(
|
||||
context: context,
|
||||
title: 'Failed to Wake Machine',
|
||||
message: 'Magic packet failed to send',
|
||||
type: SNACKBAR_TYPE.failure,
|
||||
));
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(16.0, 0.0, 0.0, 0.0),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_advanced_networkimage/transition.dart';
|
||||
import 'package:flutter_advanced_networkimage/provider.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
|
||||
class LSSliverAppBar extends StatelessWidget {
|
||||
final ScrollController _controller = ScrollController(initialScrollOffset: _EXPANDED_HEIGHT - 80.0);
|
||||
final String title;
|
||||
final String backgroundURI;
|
||||
final List<Widget> actions;
|
||||
final Widget body;
|
||||
|
||||
LSSliverAppBar({
|
||||
@required this.title,
|
||||
@required this.backgroundURI,
|
||||
@required this.body,
|
||||
this.actions,
|
||||
});
|
||||
|
||||
static const _EXPANDED_HEIGHT = 200.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => NestedScrollView(
|
||||
controller: _controller,
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
SliverOverlapAbsorber(
|
||||
sliver: SliverSafeArea(
|
||||
top: false,
|
||||
bottom: false,
|
||||
sliver: SliverAppBar(
|
||||
expandedHeight: _EXPANDED_HEIGHT,
|
||||
pinned: true,
|
||||
elevation: Constants.UI_ELEVATION,
|
||||
title: Text(
|
||||
title,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
maxLines: 1,
|
||||
),
|
||||
centerTitle: false,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
collapseMode: CollapseMode.parallax,
|
||||
background: TransitionToImage(
|
||||
image: AdvancedNetworkImage(
|
||||
backgroundURI,
|
||||
useDiskCache: true,
|
||||
fallbackAssetImage: LunaSeaDatabaseValue.THEME_AMOLED.data
|
||||
? 'assets/images/colors/black.png'
|
||||
: 'assets/images/colors/secondary.png',
|
||||
retryLimit: 1,
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
loadingWidget: Image.asset(
|
||||
LunaSeaDatabaseValue.THEME_AMOLED.data
|
||||
? 'assets/images/colors/black.png'
|
||||
: 'assets/images/colors/secondary.png',
|
||||
),
|
||||
color: LSColors.secondary.withAlpha((255/1.5).floor()),
|
||||
blendMode: BlendMode.darken,
|
||||
),
|
||||
),
|
||||
actions: actions,
|
||||
),
|
||||
),
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
),
|
||||
],
|
||||
body: body,
|
||||
);
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_advanced_networkimage/transition.dart';
|
||||
import 'package:flutter_advanced_networkimage/provider.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
|
||||
class LSSliverAppBarTabs extends StatelessWidget {
|
||||
final ScrollController _controller = ScrollController(initialScrollOffset: _EXPANDED_HEIGHT - 80.0);
|
||||
final String title;
|
||||
final String backgroundURI;
|
||||
final List<Widget> actions;
|
||||
final Widget body;
|
||||
final Widget bottom;
|
||||
|
||||
LSSliverAppBarTabs({
|
||||
@required this.title,
|
||||
@required this.backgroundURI,
|
||||
this.actions,
|
||||
@required this.body,
|
||||
@required this.bottom,
|
||||
});
|
||||
|
||||
static const _EXPANDED_HEIGHT = 200.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => NestedScrollView(
|
||||
controller: _controller,
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
SliverOverlapAbsorber(
|
||||
sliver: SliverSafeArea(
|
||||
top: false,
|
||||
bottom: false,
|
||||
sliver: SliverAppBar(
|
||||
expandedHeight: _EXPANDED_HEIGHT,
|
||||
pinned: true,
|
||||
elevation: Constants.UI_ELEVATION,
|
||||
title: Text(
|
||||
title,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
maxLines: 1,
|
||||
),
|
||||
centerTitle: false,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
collapseMode: CollapseMode.parallax,
|
||||
background: TransitionToImage(
|
||||
image: AdvancedNetworkImage(
|
||||
backgroundURI,
|
||||
useDiskCache: true,
|
||||
fallbackAssetImage: LunaSeaDatabaseValue.THEME_AMOLED.data
|
||||
? 'assets/images/colors/black.png'
|
||||
: 'assets/images/colors/secondary.png',
|
||||
retryLimit: 1,
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
loadingWidget: Image.asset(
|
||||
LunaSeaDatabaseValue.THEME_AMOLED.data
|
||||
? 'assets/images/colors/black.png'
|
||||
: 'assets/images/colors/secondary.png',
|
||||
),
|
||||
color: LSColors.secondary.withAlpha((255/1.5).floor()),
|
||||
blendMode: BlendMode.darken,
|
||||
),
|
||||
),
|
||||
actions: actions,
|
||||
bottom: bottom,
|
||||
),
|
||||
),
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
),
|
||||
],
|
||||
body: body,
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,5 @@ class RiveAnimations {
|
||||
static const CODER = {
|
||||
'path': 'assets/rive/coding.flr',
|
||||
'animation': 'coding',
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flushbar/flushbar.dart';
|
||||
import 'package:flash/flash.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
|
||||
enum SNACKBAR_TYPE {
|
||||
@@ -14,7 +14,7 @@ const _LEFT_PADDING = 8.0;
|
||||
Future<void> LSSnackBar({
|
||||
@required BuildContext context,
|
||||
@required String message,
|
||||
String title,
|
||||
@required String title,
|
||||
Duration duration,
|
||||
SNACKBAR_TYPE type = SNACKBAR_TYPE.info,
|
||||
bool showButton = false,
|
||||
@@ -28,58 +28,58 @@ Future<void> LSSnackBar({
|
||||
case SNACKBAR_TYPE.success: color = LSColors.accent; icon = Icons.check_circle_outline; break;
|
||||
case SNACKBAR_TYPE.info: color = LSColors.blue; icon = Icons.info_outline; break;
|
||||
}
|
||||
Flushbar(
|
||||
titleText: Padding(
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18.0,
|
||||
),
|
||||
),
|
||||
padding: EdgeInsets.only(left: _LEFT_PADDING),
|
||||
),
|
||||
boxShadows: [BoxShadow(blurRadius: 6.0, spreadRadius: 4.0, color: Colors.black.withOpacity(0.10))],
|
||||
messageText: Padding(
|
||||
child: Text(
|
||||
message,
|
||||
style: TextStyle(
|
||||
color: Colors.white70,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
padding: EdgeInsets.only(left: _LEFT_PADDING),
|
||||
),
|
||||
//message: message,
|
||||
showFlash(
|
||||
context: context,
|
||||
duration: duration != null
|
||||
? duration
|
||||
: showButton
|
||||
? Duration(seconds: 4)
|
||||
: Duration(seconds: 2),
|
||||
flushbarStyle: FlushbarStyle.FLOATING,
|
||||
margin: EdgeInsets.all(8),
|
||||
borderRadius: 8,
|
||||
icon: Padding(
|
||||
child: LSIconButton(
|
||||
icon: icon,
|
||||
color: color,
|
||||
),
|
||||
padding: EdgeInsets.only(left: _LEFT_PADDING),
|
||||
),
|
||||
mainButton: showButton
|
||||
? FlatButton(
|
||||
child: Text(
|
||||
buttonText.toUpperCase(),
|
||||
builder: (context, controller) => Flash(
|
||||
backgroundColor: LSColors.secondary,
|
||||
controller: controller,
|
||||
style: FlashStyle.floating,
|
||||
boxShadows: [BoxShadow(blurRadius: 6.0, spreadRadius: 4.0, color: Colors.black.withOpacity(0.10))],
|
||||
horizontalDismissDirection: HorizontalDismissDirection.horizontal,
|
||||
margin: EdgeInsets.all(8.0),
|
||||
borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS),
|
||||
child: FlashBar(
|
||||
title: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18.0,
|
||||
),
|
||||
),
|
||||
textColor: LSColors.accent,
|
||||
onPressed: buttonOnPressed,
|
||||
)
|
||||
: null,
|
||||
animationDuration: Duration(milliseconds: 375),
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
)..show(context);
|
||||
message: Text(
|
||||
message,
|
||||
style: TextStyle(
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
icon: Padding(
|
||||
child: LSIconButton(
|
||||
icon: icon,
|
||||
color: color,
|
||||
),
|
||||
padding: EdgeInsets.only(left: _LEFT_PADDING),
|
||||
),
|
||||
primaryAction: showButton
|
||||
? FlatButton(
|
||||
child: Text(
|
||||
buttonText.toUpperCase(),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
textColor: LSColors.accent,
|
||||
onPressed: () {
|
||||
controller.dismiss();
|
||||
buttonOnPressed();
|
||||
}
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,3 +6,4 @@ export './modules/sabnzbd.dart';
|
||||
export './modules/search.dart';
|
||||
export './modules/settings.dart';
|
||||
export './modules/sonarr.dart';
|
||||
export './modules/wake_on_lan.dart';
|
||||
|
||||
@@ -60,14 +60,26 @@ class CalendarAPI extends API {
|
||||
try {
|
||||
String start = DateFormat('y-MM-dd').format(today.subtract(Duration(days: startOffset)));
|
||||
String end = DateFormat('y-MM-dd').format(today.add(Duration(days: endOffset)));
|
||||
String uri = '${lidarr['host']}/api/v1/calendar?apikey=${lidarr['key']}&start=$start&end=$end';
|
||||
Dio _client = Dio();
|
||||
Map<String, dynamic> _headers = Map<String, dynamic>.from(lidarr['headers']);
|
||||
Dio _client = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: '${lidarr['host']}/api/v1/',
|
||||
queryParameters: {
|
||||
if(lidarr['key'] != '') 'apikey': lidarr['key'],
|
||||
'start': start,
|
||||
'end': end,
|
||||
},
|
||||
headers: _headers,
|
||||
followRedirects: true,
|
||||
maxRedirects: 5,
|
||||
),
|
||||
);
|
||||
if(!lidarr['strict_tls']) {
|
||||
(_client.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
|
||||
client.badCertificateCallback = (X509Certificate cert, String host, int port) => true;
|
||||
};
|
||||
}
|
||||
Response response = await _client.get(uri);
|
||||
Response response = await _client.get('calendar');
|
||||
if(response.data.length > 0) {
|
||||
for(var entry in response.data) {
|
||||
DateTime date = DateTime.tryParse(entry['releaseDate'] ?? '')?.toLocal()?.lsDateTime_floor();
|
||||
@@ -94,14 +106,26 @@ class CalendarAPI extends API {
|
||||
try {
|
||||
String start = DateFormat('y-MM-dd').format(today.subtract(Duration(days: startOffset)));
|
||||
String end = DateFormat('y-MM-dd').format(today.add(Duration(days: endOffset)));
|
||||
String uri = '${radarr['host']}/api/calendar?apikey=${radarr['key']}&start=$start&end=$end';
|
||||
Dio _client = Dio();
|
||||
Map<String, dynamic> _headers = Map<String, dynamic>.from(radarr['headers']);
|
||||
Dio _client = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: '${radarr['host']}/api/',
|
||||
queryParameters: {
|
||||
if(radarr['key'] != '') 'apikey': radarr['key'],
|
||||
'start': start,
|
||||
'end': end,
|
||||
},
|
||||
headers: _headers,
|
||||
followRedirects: true,
|
||||
maxRedirects: 5,
|
||||
),
|
||||
);
|
||||
if(!radarr['strict_tls']) {
|
||||
(_client.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
|
||||
client.badCertificateCallback = (X509Certificate cert, String host, int port) => true;
|
||||
};
|
||||
}
|
||||
Response response = await _client.get(uri);
|
||||
Response response = await _client.get('calendar');
|
||||
if(response.data.length > 0) {
|
||||
for(var entry in response.data) {
|
||||
DateTime date = DateTime.tryParse(entry['physicalRelease'] ?? '')?.toLocal()?.lsDateTime_floor();
|
||||
@@ -129,14 +153,26 @@ class CalendarAPI extends API {
|
||||
try {
|
||||
String start = DateFormat('y-MM-dd').format(today.subtract(Duration(days: startOffset)));
|
||||
String end = DateFormat('y-MM-dd').format(today.add(Duration(days: endOffset)));
|
||||
String uri = '${sonarr['host']}/api/calendar?apikey=${sonarr['key']}&start=$start&end=$end';
|
||||
Dio _client = Dio();
|
||||
Map<String, dynamic> _headers = Map<String, dynamic>.from(sonarr['headers']);
|
||||
Dio _client = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: '${sonarr['host']}/api/',
|
||||
queryParameters: {
|
||||
if(sonarr['key'] != '') 'apikey': sonarr['key'],
|
||||
'start': start,
|
||||
'end': end,
|
||||
},
|
||||
headers: _headers,
|
||||
followRedirects: true,
|
||||
maxRedirects: 5,
|
||||
),
|
||||
);
|
||||
if(!sonarr['strict_tls']) {
|
||||
(_client.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
|
||||
client.badCertificateCallback = (X509Certificate cert, String host, int port) => true;
|
||||
};
|
||||
}
|
||||
Response response = await _client.get(uri);
|
||||
Response response = await _client.get('calendar');
|
||||
if(response.data.length > 0) {
|
||||
for(var entry in response.data) {
|
||||
DateTime date = DateTime.tryParse(entry['airDateUtc'] ?? '')?.toLocal()?.lsDateTime_floor();
|
||||
|
||||
@@ -22,23 +22,35 @@ class _State extends State<Home> {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => ValueListenableBuilder(
|
||||
valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]),
|
||||
builder: (context, lunaBox, widget) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: Database.profilesBox.listenable(keys: [Database.currentProfile]),
|
||||
builder: (context, profileBox, widget) {
|
||||
if(_profileState != Database.currentProfileObject.toString()) _refreshProfile();
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
body: _body,
|
||||
drawer: _drawer,
|
||||
appBar: _appBar,
|
||||
bottomNavigationBar: _bottomNavigationBar,
|
||||
);
|
||||
}
|
||||
);
|
||||
Widget build(BuildContext context) => WillPopScope(
|
||||
onWillPop: () async {
|
||||
if(_scaffoldKey.currentState.isDrawerOpen) {
|
||||
//If the drawer is open, return true to close it
|
||||
return true;
|
||||
} else {
|
||||
//If the drawer isn't open, open the drawer
|
||||
_scaffoldKey.currentState.openDrawer();
|
||||
return false;
|
||||
}
|
||||
},
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]),
|
||||
builder: (context, lunaBox, widget) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: Database.profilesBox.listenable(keys: [Database.currentProfile]),
|
||||
builder: (context, profileBox, widget) {
|
||||
if(_profileState != Database.currentProfileObject.toString()) _refreshProfile();
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
body: _body,
|
||||
drawer: _drawer,
|
||||
appBar: _appBar,
|
||||
bottomNavigationBar: _bottomNavigationBar,
|
||||
);
|
||||
}
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
Widget get _drawer => LSDrawer(page: 'home');
|
||||
|
||||
@@ -30,21 +30,21 @@ class _State extends State<HomeQuickAccess> with AutomaticKeepAliveClientMixin {
|
||||
itemBuilder: (context, index) {
|
||||
//Check if indexer search should be included at the top
|
||||
if(index == 0 && _hasIndexers) return HomeSummaryTile(
|
||||
title: Constants.SERVICE_MAP['search']['name'],
|
||||
subtitle: Constants.SERVICE_MAP['search']['desc'],
|
||||
icon: Constants.SERVICE_MAP['search']['icon'],
|
||||
title: Constants.MODULE_MAP['search']['name'],
|
||||
subtitle: Constants.MODULE_MAP['search']['desc'],
|
||||
icon: Constants.MODULE_MAP['search']['icon'],
|
||||
index: index,
|
||||
route: Constants.SERVICE_MAP['search']['route'],
|
||||
route: Constants.MODULE_MAP['search']['route'],
|
||||
);
|
||||
if(index == _serviceCount-1) return HomeSummaryTile(
|
||||
title: Constants.SERVICE_MAP['settings']['name'],
|
||||
subtitle: Constants.SERVICE_MAP['settings']['desc'],
|
||||
icon: Constants.SERVICE_MAP['settings']['icon'],
|
||||
title: Constants.MODULE_MAP['settings']['name'],
|
||||
subtitle: Constants.MODULE_MAP['settings']['desc'],
|
||||
icon: Constants.MODULE_MAP['settings']['icon'],
|
||||
index: index,
|
||||
route: Constants.SERVICE_MAP['settings']['route'],
|
||||
route: Constants.MODULE_MAP['settings']['route'],
|
||||
justPush: true,
|
||||
);
|
||||
Map data = Constants.SERVICE_MAP[widget.profile.enabledServices[_hasIndexers ? index-1 : index]];
|
||||
Map data = Constants.MODULE_MAP[widget.profile.enabledServices[_hasIndexers ? index-1 : index]];
|
||||
if(data != null) return HomeSummaryTile(
|
||||
title: data['name'],
|
||||
subtitle: data['desc'],
|
||||
|
||||
@@ -128,17 +128,29 @@ class _State extends State<HomeCalendarWidget> with TickerProviderStateMixin {
|
||||
),
|
||||
);
|
||||
|
||||
Widget _entry(dynamic event) => LSCardTile(
|
||||
title: LSTitle(text: event.title),
|
||||
subtitle: RichText(
|
||||
text: event.subtitle,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
maxLines: 2,
|
||||
),
|
||||
trailing: event.trailing,
|
||||
onTap: () async => event.enterContent(context),
|
||||
padContent: true,
|
||||
decoration: LSCardBackground(uri: event.bannerURI),
|
||||
);
|
||||
Widget _entry(dynamic event) {
|
||||
Map headers;
|
||||
switch(event.runtimeType) {
|
||||
case CalendarLidarrData: headers = Database.currentProfileObject.getLidarr()['headers']; break;
|
||||
case CalendarRadarrData: headers = Database.currentProfileObject.getRadarr()['headers']; break;
|
||||
case CalendarSonarrData: headers = Database.currentProfileObject.getSonarr()['headers']; break;
|
||||
default: headers = {}; break;
|
||||
}
|
||||
return LSCardTile(
|
||||
title: LSTitle(text: event.title),
|
||||
subtitle: RichText(
|
||||
text: event.subtitle,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
maxLines: 2,
|
||||
),
|
||||
trailing: event.trailing,
|
||||
onTap: () async => event.enterContent(context),
|
||||
padContent: true,
|
||||
decoration: LSCardBackground(
|
||||
uri: event.bannerURI,
|
||||
headers: headers,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export './core/api.dart';
|
||||
export './core/constants.dart';
|
||||
export './core/database.dart';
|
||||
export './core/dialogs.dart';
|
||||
export './core/state.dart';
|
||||
|
||||
@@ -11,12 +11,14 @@ class LidarrAPI extends API {
|
||||
|
||||
LidarrAPI._internal(this._values, this._dio);
|
||||
factory LidarrAPI.from(ProfileHiveObject profile) {
|
||||
Map<String, dynamic> _headers = Map<String, dynamic>.from(profile.getLidarr()['headers']);
|
||||
Dio _client = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: '${profile.getLidarr()['host']}/api/v1/',
|
||||
queryParameters: {
|
||||
'apikey': profile.getLidarr()['key'],
|
||||
if(profile.getLidarr()['key'] != '') 'apikey': profile.getLidarr()['key'],
|
||||
},
|
||||
headers: _headers,
|
||||
followRedirects: true,
|
||||
maxRedirects: 5,
|
||||
),
|
||||
|
||||
@@ -3,7 +3,9 @@ import 'package:lunasea/core.dart';
|
||||
class LidarrConstants {
|
||||
LidarrConstants._();
|
||||
|
||||
static const Map SERVICE_MAP = {
|
||||
static const String MODULE_KEY = 'lidarr';
|
||||
|
||||
static const Map MODULE_MAP = {
|
||||
'name': 'Lidarr',
|
||||
'desc': 'Manage Music',
|
||||
'icon': CustomIcons.music,
|
||||
|
||||
242
lib/modules/lidarr/core/dialogs.dart
Normal file
242
lib/modules/lidarr/core/dialogs.dart
Normal file
@@ -0,0 +1,242 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
import 'package:lunasea/modules/lidarr.dart';
|
||||
|
||||
class LidarrDialogs {
|
||||
LidarrDialogs._();
|
||||
|
||||
static Future<List<dynamic>> editQualityProfile(BuildContext context, List<LidarrQualityProfile> qualities) async {
|
||||
bool _flag = false;
|
||||
LidarrQualityProfile _quality;
|
||||
|
||||
void _setValues(bool flag, LidarrQualityProfile quality) {
|
||||
_flag = flag;
|
||||
_quality = quality;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Quality Profile',
|
||||
content: List.generate(
|
||||
qualities.length,
|
||||
(index) => LSDialog.tile(
|
||||
icon: Icons.portrait,
|
||||
iconColor: LSColors.list(index),
|
||||
text: qualities[index].name,
|
||||
onTap: () => _setValues(true, qualities[index]),
|
||||
),
|
||||
),
|
||||
contentPadding: LSDialog.listDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _quality];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> editMetadataProfile(BuildContext context, List<LidarrMetadataProfile> metadatas) async {
|
||||
bool _flag = false;
|
||||
LidarrMetadataProfile _metadata;
|
||||
|
||||
void _setValues(bool flag, LidarrMetadataProfile metadata) {
|
||||
_flag = flag;
|
||||
_metadata = metadata;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Metadata Profile',
|
||||
content: List.generate(
|
||||
metadatas.length,
|
||||
(index) => LSDialog.tile(
|
||||
icon: Icons.portrait,
|
||||
iconColor: LSColors.list(index),
|
||||
text: metadatas[index].name,
|
||||
onTap: () => _setValues(true, metadatas[index]),
|
||||
),
|
||||
),
|
||||
contentPadding: LSDialog.listDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _metadata];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> deleteArtist(BuildContext context) async {
|
||||
bool _flag = false;
|
||||
bool _files = false;
|
||||
|
||||
void _setValues(bool flag, bool files) {
|
||||
_flag = flag;
|
||||
_files = files;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Remove Artist',
|
||||
buttons: [
|
||||
LSDialog.button(
|
||||
text: 'Remove + Files',
|
||||
textColor: LSColors.red,
|
||||
onPressed: () => _setValues(true, true),
|
||||
),
|
||||
LSDialog.button(
|
||||
text: 'Remove',
|
||||
textColor: LSColors.red,
|
||||
onPressed: () => _setValues(true, false),
|
||||
),
|
||||
],
|
||||
content: [
|
||||
LSDialog.textContent(text: 'Are you sure you want to remove the artist from Lidarr?'),
|
||||
],
|
||||
contentPadding: LSDialog.textDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _files];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> downloadWarning(BuildContext context) async {
|
||||
bool _flag = false;
|
||||
|
||||
void _setValues(bool flag) {
|
||||
_flag = flag;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Download Release',
|
||||
buttons: <Widget>[
|
||||
LSDialog.button(
|
||||
text: 'Download',
|
||||
onPressed: () => _setValues(true),
|
||||
),
|
||||
],
|
||||
content: [
|
||||
LSDialog.textContent(text: 'Are you sure you want to download this release? It has been marked as a rejected release by Lidarr.'),
|
||||
],
|
||||
contentPadding: LSDialog.textDialogContentPadding(),
|
||||
);
|
||||
return [_flag];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> searchAllMissing(BuildContext context) async {
|
||||
bool _flag = false;
|
||||
|
||||
void _setValues(bool flag) {
|
||||
_flag = flag;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Search All Missing',
|
||||
buttons: <Widget>[
|
||||
LSDialog.button(
|
||||
text: 'Search',
|
||||
onPressed: () => _setValues(true),
|
||||
),
|
||||
],
|
||||
content: [
|
||||
LSDialog.textContent(text: 'Are you sure you want to search for all missing albums?'),
|
||||
],
|
||||
contentPadding: LSDialog.textDialogContentPadding(),
|
||||
);
|
||||
return [_flag];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> editArtist(BuildContext context, LidarrCatalogueData entry) async {
|
||||
List<List<dynamic>> _options = [
|
||||
['Edit Artist', Icons.edit, 'edit_artist'],
|
||||
['Refresh Artist', Icons.refresh, 'refresh_artist'],
|
||||
['Remove Artist', Icons.delete, 'remove_artist'],
|
||||
];
|
||||
bool _flag = false;
|
||||
String _value = '';
|
||||
|
||||
void _setValues(bool flag, String value) {
|
||||
_flag = flag;
|
||||
_value = value;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: entry.title,
|
||||
content: List.generate(
|
||||
_options.length,
|
||||
(index) => LSDialog.tile(
|
||||
icon: _options[index][1],
|
||||
iconColor: LSColors.list(index),
|
||||
text: _options[index][0],
|
||||
onTap: () => _setValues(true, _options[index][2]),
|
||||
),
|
||||
),
|
||||
contentPadding: LSDialog.listDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _value];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> editRootFolder(BuildContext context, List<LidarrRootFolder> folders) async {
|
||||
bool _flag = false;
|
||||
LidarrRootFolder _folder;
|
||||
|
||||
void _setValues(bool flag, LidarrRootFolder folder) {
|
||||
_flag = flag;
|
||||
_folder = folder;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Root Folder',
|
||||
content: List.generate(
|
||||
folders.length,
|
||||
(index) => LSDialog.tile(
|
||||
text: folders[index].path,
|
||||
subtitle: LSDialog.richText(
|
||||
children: [
|
||||
LSDialog.bolded(text: folders[index].freeSpace.lsBytes_BytesToString()),
|
||||
],
|
||||
),
|
||||
icon: Icons.folder,
|
||||
iconColor: LSColors.list(index),
|
||||
onTap: () => _setValues(true, folders[index]),
|
||||
),
|
||||
),
|
||||
contentPadding: LSDialog.listDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _folder];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> globalSettings(BuildContext context) async {
|
||||
List<List<dynamic>> _options = [
|
||||
['View Web GUI', Icons.language, 'web_gui'],
|
||||
['Update Library', Icons.autorenew, 'update_library'],
|
||||
['Run RSS Sync', Icons.rss_feed, 'rss_sync'],
|
||||
['Search All Missing', Icons.search, 'missing_search'],
|
||||
['Backup Database', Icons.save, 'backup'],
|
||||
];
|
||||
bool _flag = false;
|
||||
String _value = '';
|
||||
|
||||
void _setValues(bool flag, String value) {
|
||||
_flag = flag;
|
||||
_value = value;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Lidarr Settings',
|
||||
content: List.generate(
|
||||
_options.length,
|
||||
(index) => LSDialog.tile(
|
||||
text: _options[index][0],
|
||||
icon: _options[index][1],
|
||||
iconColor: LSColors.list(index),
|
||||
onTap: () => _setValues(true, _options[index][2]),
|
||||
),
|
||||
),
|
||||
contentPadding: LSDialog.listDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _value];
|
||||
}
|
||||
}
|
||||
@@ -56,4 +56,12 @@ class LidarrModel extends ChangeNotifier {
|
||||
_navigationIndex = navigationIndex;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
int _artistNavigationIndex = 0;
|
||||
int get artistNavigationIndex => _artistNavigationIndex;
|
||||
set artistNavigationIndex(int artistNavigationIndex) {
|
||||
assert(artistNavigationIndex != null);
|
||||
_artistNavigationIndex = artistNavigationIndex;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,6 +147,7 @@ class _State extends State<LidarrAddDetails> {
|
||||
: _arguments.data.overview,
|
||||
uri: _arguments.data.posterURI ?? '',
|
||||
fallbackImage: 'assets/images/lidarr/noartistposter.png',
|
||||
headers: Database.currentProfileObject.getLidarr()['headers'],
|
||||
),
|
||||
LSDivider(),
|
||||
ValueListenableBuilder(
|
||||
@@ -184,7 +185,7 @@ class _State extends State<LidarrAddDetails> {
|
||||
subtitle: LSSubtitle(text: _rootfolder?.path ?? 'Unknown Root Folder'),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
onTap: () async {
|
||||
List _values = await LSDialogLidarr.showEditRootFolderPrompt(context, _rootFolders);
|
||||
List _values = await LidarrDialogs.editRootFolder(context, _rootFolders);
|
||||
if(_values[0]) box.put(LidarrDatabaseValue.ADD_ROOT_FOLDER.key, _values[1]);
|
||||
},
|
||||
);
|
||||
@@ -199,7 +200,7 @@ class _State extends State<LidarrAddDetails> {
|
||||
subtitle: LSSubtitle(text: _profile?.name ?? 'Unknown Profile'),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
onTap: () async {
|
||||
List _values = await LSDialogLidarr.showEditQualityProfilePrompt(context, _qualityProfiles);
|
||||
List _values = await LidarrDialogs.editQualityProfile(context, _qualityProfiles);
|
||||
if(_values[0]) box.put(LidarrDatabaseValue.ADD_QUALITY_PROFILE.key, _values[1]);
|
||||
},
|
||||
);
|
||||
@@ -214,7 +215,7 @@ class _State extends State<LidarrAddDetails> {
|
||||
subtitle: LSSubtitle(text: _profile?.name ?? 'Unknown Profile'),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
onTap: () async {
|
||||
List _values = await LSDialogLidarr.showEditMetadataProfilePrompt(context, _metadataProfiles);
|
||||
List _values = await LidarrDialogs.editMetadataProfile(context, _metadataProfiles);
|
||||
if(_values[0]) box.put(LidarrDatabaseValue.ADD_METADATA_PROFILE.key, _values[1]);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -22,18 +22,16 @@ class LidarrDetailsArtist extends StatefulWidget {
|
||||
|
||||
class _State extends State<LidarrDetailsArtist> {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
final _pageController = PageController(initialPage: 1);
|
||||
LidarrDetailsArtistArguments _arguments;
|
||||
bool _error = false;
|
||||
final List<String> _tabTitles = [
|
||||
'Overview',
|
||||
'Albums',
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
_arguments = ModalRoute.of(context).settings.arguments;
|
||||
Provider.of<LidarrModel>(context, listen: false).artistNavigationIndex = 1;
|
||||
_fetch();
|
||||
});
|
||||
}
|
||||
@@ -51,46 +49,48 @@ class _State extends State<LidarrDetailsArtist> {
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: _arguments == null || _arguments.data == null ? _appBar : null,
|
||||
appBar: _appBar,
|
||||
bottomNavigationBar: _arguments != null && _arguments.data != null
|
||||
? _bottomNavigationBar
|
||||
: null,
|
||||
body: _arguments != null
|
||||
? _arguments.data != null
|
||||
? _body
|
||||
: _error
|
||||
: _error
|
||||
? LSErrorMessage(onTapHandler: () => _fetch())
|
||||
: LSLoading()
|
||||
: null,
|
||||
);
|
||||
|
||||
Widget get _appBar => LSAppBar(title: 'Artist Details');
|
||||
|
||||
Widget get _body => DefaultTabController(
|
||||
length: _tabTitles.length,
|
||||
initialIndex: 1,
|
||||
child: LSSliverAppBarTabs(
|
||||
title: _arguments.data.title,
|
||||
backgroundURI: _arguments.data.fanartURI(highRes: true),
|
||||
bottom: TabBar(
|
||||
tabs: <Widget>[
|
||||
for(int i =0; i<_tabTitles.length; i++) Tab(
|
||||
child: Text(_tabTitles[i]),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: TabBarView(
|
||||
children: <Widget>[
|
||||
LidarrDetailsOverview(data: _arguments.data),
|
||||
LidarrDetailsAlbumList(artistID: _arguments.data.artistID),
|
||||
],
|
||||
),
|
||||
actions: <Widget>[
|
||||
Widget get _appBar => LSAppBar(
|
||||
title: _arguments == null || _arguments.data == null
|
||||
? 'Artist Details'
|
||||
: _arguments.data.title,
|
||||
actions: _arguments == null || _arguments.data == null
|
||||
? null
|
||||
: <Widget>[
|
||||
LidarrDetailsHideButton(),
|
||||
LidarrDetailsEditButton(
|
||||
data: _arguments.data,
|
||||
remove: (bool withData) => _removeCallback(withData),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Widget get _bottomNavigationBar => LidarrArtistNavigationBar(pageController: _pageController);
|
||||
|
||||
List<Widget> get _tabs => [
|
||||
LidarrDetailsOverview(data: _arguments.data),
|
||||
LidarrDetailsAlbumList(artistID: _arguments.data.artistID),
|
||||
];
|
||||
|
||||
Widget get _body => PageView(
|
||||
controller: _pageController,
|
||||
children: _tabs,
|
||||
onPageChanged: _onPageChanged,
|
||||
);
|
||||
|
||||
void _onPageChanged(int index) => Provider.of<LidarrModel>(context, listen: false).artistNavigationIndex = index;
|
||||
|
||||
Future<void> _removeCallback(bool withData) async => Navigator.of(context).pop(['remove_artist', withData]);
|
||||
}
|
||||
|
||||
@@ -157,17 +157,17 @@ class _State extends State<LidarrEditArtist> {
|
||||
);
|
||||
|
||||
Future<void> _changePath() async {
|
||||
List<dynamic> _values = await LSDialogSystem.editText(context, 'Artist Path', prefill: _path);
|
||||
List<dynamic> _values = await GlobalDialogs.editText(context, 'Artist Path', prefill: _path);
|
||||
if(_values[0] && mounted) setState(() => _path = _values[1]);
|
||||
}
|
||||
|
||||
Future<void> _changeProfile() async {
|
||||
List<dynamic> _values = await LSDialogLidarr.showEditQualityProfilePrompt(context, _qualityProfiles);
|
||||
List<dynamic> _values = await LidarrDialogs.editQualityProfile(context, _qualityProfiles);
|
||||
if(_values[0] && mounted) setState(() => _qualityProfile = _values[1]);
|
||||
}
|
||||
|
||||
Future<void> _changeMetadata() async {
|
||||
List<dynamic> _values = await LSDialogLidarr.showEditMetadataProfilePrompt(context, _metadataProfiles);
|
||||
List<dynamic> _values = await LidarrDialogs.editMetadataProfile(context, _metadataProfiles);
|
||||
if(_values[0] && mounted) setState(() => _metadataProfile = _values[1]);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,18 +28,30 @@ class _State extends State<Lidarr> {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => ValueListenableBuilder(
|
||||
valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]),
|
||||
builder: (context, box, widget) {
|
||||
if(_profileState != Database.currentProfileObject.toString()) _refreshProfile();
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
body: _body,
|
||||
drawer: _drawer,
|
||||
appBar: _appBar,
|
||||
bottomNavigationBar: _bottomNavigationBar,
|
||||
);
|
||||
Widget build(BuildContext context) => WillPopScope(
|
||||
onWillPop: () async {
|
||||
if(_scaffoldKey.currentState.isDrawerOpen) {
|
||||
//If the drawer is open, return true to close it
|
||||
return true;
|
||||
} else {
|
||||
//If the drawer isn't open, open the drawer
|
||||
_scaffoldKey.currentState.openDrawer();
|
||||
return false;
|
||||
}
|
||||
},
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]),
|
||||
builder: (context, box, widget) {
|
||||
if(_profileState != Database.currentProfileObject.toString()) _refreshProfile();
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
body: _body,
|
||||
drawer: _drawer,
|
||||
appBar: _appBar,
|
||||
bottomNavigationBar: _bottomNavigationBar,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
Widget get _drawer => LSDrawer(page: 'lidarr');
|
||||
@@ -103,7 +115,7 @@ class _State extends State<Lidarr> {
|
||||
}
|
||||
|
||||
Future<void> _handlePopup() async {
|
||||
List<dynamic> values = await LSDialogLidarr.showSettingsPrompt(context);
|
||||
List<dynamic> values = await LidarrDialogs.globalSettings(context);
|
||||
if(values[0]) switch(values[1]) {
|
||||
case 'web_gui': await _api.host?.toString()?.lsLinks_OpenLink(); break;
|
||||
case 'update_library': await _api.updateLibrary()
|
||||
@@ -119,7 +131,7 @@ class _State extends State<Lidarr> {
|
||||
.catchError((_) => LSSnackBar(context: context, title: 'Failed to Backup Database', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure));
|
||||
break;
|
||||
case 'missing_search': {
|
||||
List<dynamic> values = await LSDialogLidarr.showSearchMissingPrompt(context);
|
||||
List<dynamic> values = await LidarrDialogs.searchAllMissing(context);
|
||||
if(values[0]) await _api.searchAllMissing()
|
||||
.then((_) => LSSnackBar(context: context, title: 'Searching...', message: 'Search for all missing albums'))
|
||||
.catchError((_) => LSSnackBar(context: context, title: 'Failed to Search', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure));
|
||||
|
||||
@@ -63,7 +63,7 @@ class _State extends State<LidarrSearchDetails> {
|
||||
title: LSTitle(text: 'Release Title'),
|
||||
subtitle: LSSubtitle(text: _arguments.data.title),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
onTap: () async => LSDialogSystem.textPreview(context, 'Release Title', _arguments.data.title),
|
||||
onTap: () async => GlobalDialogs.textPreview(context, 'Release Title', _arguments.data.title),
|
||||
),
|
||||
LSContainerRow(
|
||||
children: <Widget>[
|
||||
@@ -156,42 +156,22 @@ class _State extends State<LidarrSearchDetails> {
|
||||
Future<void> _warningAction() async => _showWarnings();
|
||||
|
||||
Future<void> _downloadAction() async {
|
||||
if(_arguments.data.approved) {
|
||||
await _startDownload()
|
||||
.then((_) => LSSnackBar(
|
||||
context: context,
|
||||
title: 'Downloading...',
|
||||
message: _arguments.data.title,
|
||||
type: SNACKBAR_TYPE.success,
|
||||
showButton: true,
|
||||
buttonText: 'Back',
|
||||
buttonOnPressed: () => Navigator.of(context).popUntil(ModalRoute.withName(Lidarr.ROUTE_NAME)),
|
||||
))
|
||||
.catchError((_) => LSSnackBar(
|
||||
context: context,
|
||||
title: 'Failed to Start Downloading',
|
||||
message: Constants.CHECK_LOGS_MESSAGE,
|
||||
type: SNACKBAR_TYPE.failure,
|
||||
));
|
||||
} else {
|
||||
List<dynamic> values = await LSDialogLidarr.showDownloadWarningPrompt(context);
|
||||
if(values[0]) await _startDownload()
|
||||
.then((_) => LSSnackBar(
|
||||
context: context,
|
||||
title: 'Downloading...',
|
||||
message: _arguments.data.title,
|
||||
type: SNACKBAR_TYPE.success,
|
||||
showButton: true,
|
||||
buttonText: 'Back',
|
||||
buttonOnPressed: () => Navigator.of(context).popUntil(ModalRoute.withName(Lidarr.ROUTE_NAME)),
|
||||
))
|
||||
.catchError((_) => LSSnackBar(
|
||||
context: context,
|
||||
title: 'Failed to Start Downloading',
|
||||
message: Constants.CHECK_LOGS_MESSAGE,
|
||||
type: SNACKBAR_TYPE.failure,
|
||||
));
|
||||
}
|
||||
await _startDownload()
|
||||
.then((_) => LSSnackBar(
|
||||
context: context,
|
||||
title: 'Downloading...',
|
||||
message: _arguments.data.title,
|
||||
type: SNACKBAR_TYPE.success,
|
||||
showButton: true,
|
||||
buttonText: 'Back',
|
||||
buttonOnPressed: () => Navigator.of(context).popUntil(ModalRoute.withName(Lidarr.ROUTE_NAME)),
|
||||
))
|
||||
.catchError((_) => LSSnackBar(
|
||||
context: context,
|
||||
title: 'Failed to Start Downloading',
|
||||
message: Constants.CHECK_LOGS_MESSAGE,
|
||||
type: SNACKBAR_TYPE.failure,
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> _showWarnings() async {
|
||||
@@ -199,7 +179,7 @@ class _State extends State<LidarrSearchDetails> {
|
||||
for(var i=0; i<_arguments.data.rejections.length; i++) {
|
||||
reject += '${i+1}. ${_arguments.data.rejections[i]}\n';
|
||||
}
|
||||
await LSDialogSystem.textPreview(context, 'Rejection Reasons', reject.substring(0, reject.length-1));
|
||||
await GlobalDialogs.textPreview(context, 'Rejection Reasons', reject.substring(0, reject.length-1));
|
||||
}
|
||||
|
||||
Future<bool> _startDownload() async {
|
||||
|
||||
@@ -14,3 +14,4 @@ export './widgets/search_result_tile.dart';
|
||||
export './widgets/add_search_bar.dart';
|
||||
export './widgets/add_search_result_tile.dart';
|
||||
export './widgets/navigation_bar.dart';
|
||||
export './widgets/artist_navigation_bar.dart';
|
||||
|
||||
46
lib/modules/lidarr/widgets/artist_navigation_bar.dart
Normal file
46
lib/modules/lidarr/widgets/artist_navigation_bar.dart
Normal file
@@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
import '../../lidarr.dart';
|
||||
|
||||
class LidarrArtistNavigationBar extends StatefulWidget {
|
||||
final PageController pageController;
|
||||
|
||||
LidarrArtistNavigationBar({
|
||||
Key key,
|
||||
@required this.pageController,
|
||||
}): super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _State();
|
||||
}
|
||||
|
||||
class _State extends State<LidarrArtistNavigationBar> {
|
||||
static const List<String> _navbarTitles = [
|
||||
'Overview',
|
||||
'Albums',
|
||||
];
|
||||
|
||||
static const List<IconData> _navbarIcons = [
|
||||
Icons.subject,
|
||||
CustomIcons.music,
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Selector<LidarrModel, int>(
|
||||
selector: (_, model) => model.artistNavigationIndex,
|
||||
builder: (context, index, _) => LSBottomNavigationBar(
|
||||
index: index,
|
||||
icons: _navbarIcons,
|
||||
titles: _navbarTitles,
|
||||
onTap: (index) async => await _navOnTap(index),
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> _navOnTap(int index) async {
|
||||
await widget.pageController.animateToPage(
|
||||
index,
|
||||
duration: Duration(milliseconds: Constants.UI_NAVIGATION_SPEED),
|
||||
curve: Curves.easeOutSine,
|
||||
).then((_) => Provider.of<LidarrModel>(context, listen: false).artistNavigationIndex = index);
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ class _State extends State<LidarrCatalogueTile> {
|
||||
padContent: true,
|
||||
decoration: LSCardBackground(
|
||||
uri: widget.data.bannerURI(),
|
||||
headers: Database.currentProfileObject.getLidarr()['headers'],
|
||||
darken: !widget.data.monitored,
|
||||
),
|
||||
onTap: () async => _enterArtist(),
|
||||
@@ -96,7 +97,7 @@ class _State extends State<LidarrCatalogueTile> {
|
||||
}
|
||||
|
||||
Future<void> _handlePopup() async {
|
||||
List<dynamic> values = await LSDialogLidarr.showEditArtistPrompt(context, widget.data);
|
||||
List<dynamic> values = await LidarrDialogs.editArtist(context, widget.data);
|
||||
if(values[0]) switch(values[1]) {
|
||||
case 'refresh_artist': _refreshArtist(); break;
|
||||
case 'edit_artist': _enterEditArtist(); break;
|
||||
@@ -127,10 +128,10 @@ class _State extends State<LidarrCatalogueTile> {
|
||||
|
||||
Future<void> _removeArtist() async {
|
||||
final _api = LidarrAPI.from(Database.currentProfileObject);
|
||||
List values = await LSDialogLidarr.showDeleteArtistPrompt(context);
|
||||
List values = await LidarrDialogs.deleteArtist(context);
|
||||
if(values[0]) {
|
||||
if(values[1]) {
|
||||
values = await LSDialogSystem.deleteCatalogueWithFiles(context, widget.data.title);
|
||||
values = await GlobalDialogs.deleteCatalogueWithFiles(context, widget.data.title);
|
||||
if(values[0]) {
|
||||
await _api.removeArtist(widget.data.artistID, deleteFiles: true)
|
||||
.then((_) {
|
||||
|
||||
@@ -76,7 +76,7 @@ class _State extends State<LidarrDetailsAlbumList> with AutomaticKeepAliveClient
|
||||
data: _filtered[index],
|
||||
refreshState: _refreshState,
|
||||
),
|
||||
padBottom: true,
|
||||
padBottom: false,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -30,6 +30,7 @@ class _State extends State<LidarrDetailsAlbumTile> {
|
||||
child: TransitionToImage(
|
||||
image: AdvancedNetworkImage(
|
||||
widget.data.albumCoverURI(),
|
||||
header: Map<String, String>.from(Database.currentProfileObject.getLidarr()['headers']),
|
||||
useDiskCache: true,
|
||||
fallbackAssetImage: 'assets/images/lidarr/noalbumart.png',
|
||||
retryLimit: 1,
|
||||
|
||||
@@ -25,7 +25,7 @@ class _State extends State<LidarrDetailsEditButton> {
|
||||
);
|
||||
|
||||
Future<void> _handlePopup(BuildContext context) async {
|
||||
List<dynamic> values = await LSDialogLidarr.showEditArtistPrompt(context, widget.data);
|
||||
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;
|
||||
@@ -56,10 +56,10 @@ class _State extends State<LidarrDetailsEditButton> {
|
||||
|
||||
Future<void> _removeArtist(BuildContext context) async {
|
||||
final _api = LidarrAPI.from(Database.currentProfileObject);
|
||||
List values = await LSDialogLidarr.showDeleteArtistPrompt(context);
|
||||
List values = await LidarrDialogs.deleteArtist(context);
|
||||
if(values[0]) {
|
||||
if(values[1]) {
|
||||
values = await LSDialogSystem.deleteCatalogueWithFiles(context, widget.data.title);
|
||||
values = await GlobalDialogs.deleteCatalogueWithFiles(context, widget.data.title);
|
||||
if(values[0]) {
|
||||
await _api.removeArtist(widget.data.artistID, deleteFiles: true)
|
||||
.then((_) => widget.remove(true))
|
||||
|
||||
@@ -30,11 +30,12 @@ class _State extends State<LidarrDetailsOverview> with AutomaticKeepAliveClientM
|
||||
: widget?.data?.overview,
|
||||
uri: widget?.data?.posterURI() ?? '',
|
||||
fallbackImage: 'assets/images/lidarr/noartistposter.png',
|
||||
headers: Database.currentProfileObject.getLidarr()['headers'],
|
||||
),
|
||||
LSCardTile(
|
||||
title: LSTitle(text: 'Artist Path', centerText: true),
|
||||
subtitle: LSSubtitle(text: widget?.data?.path ?? 'Unknown', centerText: true),
|
||||
onTap: () => LSDialogSystem.textPreview(context, 'Artist Path', widget?.data?.path ?? 'Unknown'),
|
||||
onTap: () => GlobalDialogs.textPreview(context, 'Artist Path', widget?.data?.path ?? 'Unknown'),
|
||||
),
|
||||
LSContainerRow(
|
||||
children: <Widget>[
|
||||
|
||||
@@ -50,7 +50,10 @@ class _State extends State<LidarrMissingTile> {
|
||||
),
|
||||
onTap: () async => _enterAlbum(),
|
||||
onLongPress: () async => _enterArtist(),
|
||||
decoration: LSCardBackground(uri: widget.entry.bannerURI()),
|
||||
decoration: LSCardBackground(
|
||||
uri: widget.entry.bannerURI(),
|
||||
headers: Database.currentProfileObject.getLidarr()['headers'],
|
||||
),
|
||||
padContent: true,
|
||||
);
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ class LidarrSearchResultTile extends StatelessWidget {
|
||||
|
||||
Future<void> _trailingLongPressed(BuildContext context) async {
|
||||
if(!data.approved) {
|
||||
List<dynamic> values = await LSDialogLidarr.showDownloadWarningPrompt(context);
|
||||
List<dynamic> values = await LidarrDialogs.downloadWarning(context);
|
||||
if(values[0]) await _startDownload()
|
||||
.then((_) => LSSnackBar(
|
||||
context: context,
|
||||
@@ -112,7 +112,7 @@ class LidarrSearchResultTile extends StatelessWidget {
|
||||
for(var i=0; i<data.rejections.length; i++) {
|
||||
reject += '${i+1}. ${data.rejections[i]}\n';
|
||||
}
|
||||
await LSDialogSystem.textPreview(context, 'Rejection Reasons', reject.substring(0, reject.length-1));
|
||||
await GlobalDialogs.textPreview(context, 'Rejection Reasons', reject.substring(0, reject.length-1));
|
||||
}
|
||||
|
||||
Future<bool> _startDownload() async {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export './nzbget/api.dart';
|
||||
export './nzbget/core.dart';
|
||||
export './nzbget/routes.dart';
|
||||
export './nzbget/state.dart';
|
||||
export './nzbget/widgets.dart';
|
||||
|
||||
5
lib/modules/nzbget/core.dart
Normal file
5
lib/modules/nzbget/core.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
export './core/api.dart';
|
||||
export './core/constants.dart';
|
||||
export './core/database.dart';
|
||||
export './core/dialogs.dart';
|
||||
export './core/state.dart';
|
||||
@@ -4,7 +4,7 @@ import 'dart:math';
|
||||
import 'package:dio/adapter.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
import '../../nzbget.dart';
|
||||
import 'package:lunasea/modules/nzbget.dart';
|
||||
|
||||
class NZBGetAPI extends API {
|
||||
final Map<String, dynamic> _values;
|
||||
@@ -13,7 +13,7 @@ class NZBGetAPI extends API {
|
||||
NZBGetAPI._internal(this._values, this._dio);
|
||||
factory NZBGetAPI.from(ProfileHiveObject profile) {
|
||||
String _baseURL = Uri.encodeFull(profile.getNZBGet()['host']);
|
||||
Map<String, dynamic> _headers = {};
|
||||
Map<String, dynamic> _headers = Map<String, dynamic>.from(profile.getNZBGet()['headers']);
|
||||
if(profile.getNZBGet()['basic_auth']) {
|
||||
String _auth = base64.encode(utf8.encode('${profile.getNZBGet()['user']}:${profile.getNZBGet()['pass']}'));
|
||||
_headers['Authorization'] = 'Basic $_auth';
|
||||
14
lib/modules/nzbget/core/constants.dart
Normal file
14
lib/modules/nzbget/core/constants.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:lunasea/core.dart';
|
||||
|
||||
class NZBGetConstants {
|
||||
NZBGetConstants._();
|
||||
|
||||
static const String MODULE_KEY = 'nzbget';
|
||||
|
||||
static const Map MODULE_MAP = {
|
||||
'name': 'NZBGet',
|
||||
'desc': 'Manage Usenet Downloads',
|
||||
'icon': CustomIcons.nzbget,
|
||||
'route': '/nzbget',
|
||||
};
|
||||
}
|
||||
3
lib/modules/nzbget/core/database.dart
Normal file
3
lib/modules/nzbget/core/database.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
class NZBGetDatabase {
|
||||
NZBGetDatabase._();
|
||||
}
|
||||
568
lib/modules/nzbget/core/dialogs.dart
Normal file
568
lib/modules/nzbget/core/dialogs.dart
Normal file
@@ -0,0 +1,568 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
import 'package:lunasea/modules/nzbget.dart';
|
||||
|
||||
class NZBGetDialogs {
|
||||
NZBGetDialogs._();
|
||||
|
||||
static Future<List<dynamic>> globalSettings(BuildContext context) async {
|
||||
List<List<dynamic>> _options = [
|
||||
['View Web GUI', Icons.language, 'web_gui'],
|
||||
['Add NZB', Icons.add, 'add_nzb'],
|
||||
['Sort Queue', Icons.sort, 'sort'],
|
||||
['Status & Statistics', Icons.info_outline, 'server_details'],
|
||||
];
|
||||
bool _flag = false;
|
||||
String _value = '';
|
||||
|
||||
void _setValues(bool flag, String value) {
|
||||
_flag = flag;
|
||||
_value = value;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'NZBGet Settings',
|
||||
content: List.generate(
|
||||
_options.length,
|
||||
(index) => LSDialog.tile(
|
||||
text: _options[index][0],
|
||||
icon: _options[index][1],
|
||||
iconColor: LSColors.list(index),
|
||||
onTap: () => _setValues(true, _options[index][2]),
|
||||
),
|
||||
),
|
||||
contentPadding: LSDialog.listDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _value];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> queueSettings(BuildContext context, String title, bool isPaused) async {
|
||||
List<List<dynamic>> _options = [
|
||||
isPaused
|
||||
? ['Resume Job', Icons.play_arrow, 'status']
|
||||
: ['Pause Job', Icons.pause, 'status'],
|
||||
['Change Category', Icons.category, 'category'],
|
||||
['Change Priority', Icons.low_priority, 'priority'],
|
||||
['Set Password', Icons.vpn_key, 'password'],
|
||||
['Rename Job', Icons.text_format, 'rename'],
|
||||
['Delete Job', Icons.delete, 'delete'],
|
||||
];
|
||||
bool _flag = false;
|
||||
String _value = '';
|
||||
|
||||
void _setValues(bool flag, String value) {
|
||||
_flag = flag;
|
||||
_value = value;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: title,
|
||||
content: List.generate(
|
||||
_options.length,
|
||||
(index) => LSDialog.tile(
|
||||
text: _options[index][0],
|
||||
icon: _options[index][1],
|
||||
iconColor: LSColors.list(index),
|
||||
onTap: () => _setValues(true, _options[index][2]),
|
||||
),
|
||||
),
|
||||
contentPadding: LSDialog.listDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _value];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> deleteJob(BuildContext context) async {
|
||||
bool _flag = false;
|
||||
|
||||
void _setValues(bool flag) {
|
||||
_flag = flag;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Delete Job',
|
||||
buttons: [
|
||||
LSDialog.button(
|
||||
text: 'Delete',
|
||||
textColor: LSColors.red,
|
||||
onPressed: () => _setValues(true),
|
||||
),
|
||||
],
|
||||
content: [
|
||||
LSDialog.textContent(text: 'Are you sure you want to delete this job?'),
|
||||
],
|
||||
contentPadding: LSDialog.textDialogContentPadding(),
|
||||
);
|
||||
return [_flag];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> renameJob(BuildContext context, String originalName) async {
|
||||
bool _flag = false;
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _textController = TextEditingController()..text = originalName;
|
||||
|
||||
void _setValues(bool flag) {
|
||||
if(_formKey.currentState.validate()) {
|
||||
_flag = flag;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Rename Job',
|
||||
buttons: [
|
||||
LSDialog.button(
|
||||
text: 'Rename',
|
||||
onPressed: () => _setValues(true),
|
||||
),
|
||||
],
|
||||
content: [
|
||||
Form(
|
||||
key: _formKey,
|
||||
child: LSDialog.textFormInput(
|
||||
controller: _textController,
|
||||
title: 'Job Name',
|
||||
onSubmitted: (_) => _setValues(true),
|
||||
validator: (value) => value.length < 1
|
||||
? 'Please enter a valid name'
|
||||
: null,
|
||||
),
|
||||
),
|
||||
],
|
||||
contentPadding: LSDialog.inputDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _textController.text];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> changePriority(BuildContext context) async {
|
||||
bool _flag = false;
|
||||
NZBGetPriority _priority;
|
||||
|
||||
void _setValues(bool flag, NZBGetPriority priority) {
|
||||
_flag = flag;
|
||||
_priority = priority;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Change Priority',
|
||||
content: List.generate(
|
||||
NZBGetPriority.values.length,
|
||||
(index) => LSDialog.tile(
|
||||
text: NZBGetPriority.values[index].name,
|
||||
icon: Icons.low_priority,
|
||||
iconColor: LSColors.list(index),
|
||||
onTap: () => _setValues(true, NZBGetPriority.values[index]),
|
||||
),
|
||||
),
|
||||
contentPadding: LSDialog.listDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _priority];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> changeCategory(BuildContext context, List<NZBGetCategoryData> categories) async {
|
||||
bool _flag = false;
|
||||
NZBGetCategoryData _category;
|
||||
|
||||
void _setValues(bool flag, NZBGetCategoryData category) {
|
||||
_flag = flag;
|
||||
_category = category;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Change Category',
|
||||
content: List.generate(
|
||||
categories.length,
|
||||
(index) => LSDialog.tile(
|
||||
text: categories[index].name,
|
||||
icon: Icons.category,
|
||||
iconColor: LSColors.list(index),
|
||||
onTap: () => _setValues(true, categories[index]),
|
||||
),
|
||||
),
|
||||
contentPadding: LSDialog.listDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _category];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> setPassword(BuildContext context) async {
|
||||
bool _flag = false;
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _textController = TextEditingController();
|
||||
|
||||
void _setValues(bool flag) {
|
||||
if(_formKey.currentState.validate()) {
|
||||
_flag = flag;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Set Password',
|
||||
buttons: [
|
||||
LSDialog.button(
|
||||
text: 'Set',
|
||||
onPressed: () => _setValues(true),
|
||||
),
|
||||
],
|
||||
content: [
|
||||
Form(
|
||||
key: _formKey,
|
||||
child: LSDialog.textFormInput(
|
||||
controller: _textController,
|
||||
title: 'Job Password',
|
||||
onSubmitted: (_) => _setValues(true),
|
||||
validator: (value) => value.length < 1
|
||||
? 'Please enter a valid password'
|
||||
: null,
|
||||
obscureText: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
contentPadding: LSDialog.inputDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _textController.text];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> deleteHistory(BuildContext context) async {
|
||||
bool _flag = false;
|
||||
bool _hide = false;
|
||||
|
||||
void _setValues(bool flag, bool hide) {
|
||||
_flag = flag;
|
||||
_hide = hide;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Remove History',
|
||||
buttons: [
|
||||
LSDialog.button(
|
||||
text: 'Hide',
|
||||
onPressed: () => _setValues(true, true),
|
||||
),
|
||||
LSDialog.button(
|
||||
text: 'Delete',
|
||||
textColor: LSColors.red,
|
||||
onPressed: () => _setValues(true, false),
|
||||
),
|
||||
],
|
||||
content: [
|
||||
LSDialog.textContent(text: 'Are you sure you want to remove the history for this job?'),
|
||||
],
|
||||
contentPadding: LSDialog.textDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _hide];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> sortQueue(BuildContext context) async {
|
||||
bool _flag = false;
|
||||
NZBGetSort _sort;
|
||||
|
||||
void _setValues(bool flag, NZBGetSort sort) {
|
||||
_flag = flag;
|
||||
_sort = sort;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Sort Queue',
|
||||
content: List.generate(
|
||||
NZBGetSort.values.length,
|
||||
(index) => LSDialog.tile(
|
||||
text: NZBGetSort.values[index].name,
|
||||
icon: NZBGetSort.values[index].icon,
|
||||
iconColor: LSColors.list(index),
|
||||
onTap: () => _setValues(true, NZBGetSort.values[index]),
|
||||
),
|
||||
),
|
||||
contentPadding: LSDialog.listDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _sort];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> addNZB(BuildContext context) async {
|
||||
List<List<dynamic>> _options = [
|
||||
['Add by URL', Icons.link, 'link'],
|
||||
['Add by File', Icons.sd_card, 'file'],
|
||||
];
|
||||
bool _flag = false;
|
||||
String _type = '';
|
||||
|
||||
void _setValues(bool flag, String type) {
|
||||
_flag = flag;
|
||||
_type = type;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Add NZB',
|
||||
content: List.generate(
|
||||
_options.length,
|
||||
(index) => LSDialog.tile(
|
||||
text: _options[index][0],
|
||||
icon: _options[index][1],
|
||||
iconColor: LSColors.list(index),
|
||||
onTap: () => _setValues(true, _options[index][2]),
|
||||
),
|
||||
),
|
||||
contentPadding: LSDialog.listDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _type];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> addNZBUrl(BuildContext context) async {
|
||||
bool _flag = false;
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _textController = TextEditingController();
|
||||
|
||||
void _setValues(bool flag) {
|
||||
if(_formKey.currentState.validate()) {
|
||||
_flag = flag;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Add NZB by URL',
|
||||
buttons: [
|
||||
LSDialog.button(
|
||||
text: 'Add',
|
||||
onPressed: () => _setValues(true),
|
||||
),
|
||||
],
|
||||
content: [
|
||||
Form(
|
||||
key: _formKey,
|
||||
child: LSDialog.textFormInput(
|
||||
controller: _textController,
|
||||
title: 'NZB URL',
|
||||
keyboardType: TextInputType.url,
|
||||
onSubmitted: (_) => _setValues(true),
|
||||
validator: (value) => !value.startsWith('http://') && !value.startsWith('https://')
|
||||
? 'Please enter a valid URL'
|
||||
: null,
|
||||
),
|
||||
),
|
||||
],
|
||||
contentPadding: LSDialog.inputDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _textController.text];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> speedLimit(BuildContext context, String currentSpeed) async {
|
||||
List<List<dynamic>> _options = [
|
||||
['Unlimited', Icons.timeline, 0],
|
||||
['125 MB/s', Icons.timeline, 128000],
|
||||
['100 MB/s', Icons.timeline, 102400],
|
||||
['75 MB/s', Icons.timeline, 76800],
|
||||
['50 MB/s', Icons.timeline, 51200],
|
||||
['25 MB/s', Icons.timeline, 25600],
|
||||
['10 MB/s', Icons.timeline, 10240],
|
||||
['Custom...', Icons.timeline, -1],
|
||||
];
|
||||
bool _flag = false;
|
||||
int _limit = 0;
|
||||
|
||||
void _setValues(bool flag, int limit) {
|
||||
_flag = flag;
|
||||
_limit = limit;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: currentSpeed == 'Unlimited' ? 'Speed Limit (Unlimited)' : 'Speed Limit ($currentSpeed/s)',
|
||||
content: List.generate(
|
||||
_options.length,
|
||||
(index) => LSDialog.tile(
|
||||
text: _options[index][0],
|
||||
icon: _options[index][1],
|
||||
iconColor: LSColors.list(index),
|
||||
onTap: () => _setValues(true, _options[index][2]),
|
||||
),
|
||||
),
|
||||
contentPadding: LSDialog.listDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _limit];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> customSpeedLimit(BuildContext context) async {
|
||||
bool _flag = false;
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _textController = TextEditingController();
|
||||
|
||||
void _setValues(bool flag) {
|
||||
if(_formKey.currentState.validate()) {
|
||||
_flag = flag;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Custom Speed Limit',
|
||||
buttons: [
|
||||
LSDialog.button(
|
||||
text: 'Set',
|
||||
onPressed: () => _setValues(true),
|
||||
),
|
||||
],
|
||||
content: [
|
||||
LSDialog.textContent(text: 'Please enter a speed limit in KB/s.'),
|
||||
Form(
|
||||
key: _formKey,
|
||||
child: LSDialog.textFormInput(
|
||||
controller: _textController,
|
||||
title: 'Speed Limit',
|
||||
onSubmitted: (_) => _setValues(true),
|
||||
keyboardType: TextInputType.number,
|
||||
validator: (value) {
|
||||
int _value = int.tryParse(value);
|
||||
if(_value == null || _value < 1) {
|
||||
return 'Must be a number greater than 1';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
contentPadding: LSDialog.inputTextDialogContentPadding(),
|
||||
);
|
||||
return [_flag, int.tryParse(_textController.text)];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> historySettings(BuildContext context, String title) async {
|
||||
List<List<dynamic>> _options = [
|
||||
['Retry Job', Icons.autorenew, 'retry'],
|
||||
['Hide History', Icons.visibility_off, 'hide'],
|
||||
['Delete History', Icons.delete, 'delete'],
|
||||
];
|
||||
bool _flag = false;
|
||||
String _value = '';
|
||||
|
||||
void _setValues(bool flag, String value) {
|
||||
_flag = flag;
|
||||
_value = value;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: title,
|
||||
content: List.generate(
|
||||
_options.length,
|
||||
(index) => LSDialog.tile(
|
||||
text: _options[index][0],
|
||||
icon: _options[index][1],
|
||||
iconColor: LSColors.list(index),
|
||||
onTap: () => _setValues(true, _options[index][2]),
|
||||
),
|
||||
),
|
||||
contentPadding: LSDialog.listDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _value];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> pauseFor(BuildContext context) async {
|
||||
List<List<dynamic>> _options = [
|
||||
['5 Minutes', Icons.access_time, 5],
|
||||
['15 Minutes', Icons.access_time, 15],
|
||||
['30 Minutes', Icons.access_time, 30],
|
||||
['1 Hour', Icons.access_time, 60],
|
||||
['3 Hours', Icons.access_time, 180],
|
||||
['6 Hours', Icons.access_time, 360],
|
||||
['Custom...', Icons.access_time, -1],
|
||||
];
|
||||
bool _flag = false;
|
||||
int _duration = 0;
|
||||
|
||||
void _setValues(bool flag, int duration) {
|
||||
_flag = flag;
|
||||
_duration = duration;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Pause Queue For...',
|
||||
content: List.generate(
|
||||
_options.length,
|
||||
(index) => LSDialog.tile(
|
||||
text: _options[index][0],
|
||||
icon: _options[index][1],
|
||||
iconColor: LSColors.list(index),
|
||||
onTap: () => _setValues(true, _options[index][2]),
|
||||
),
|
||||
),
|
||||
contentPadding: LSDialog.listDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _duration];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> customPauseFor(BuildContext context) async {
|
||||
bool _flag = false;
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _textController = TextEditingController();
|
||||
|
||||
void _setValues(bool flag) {
|
||||
if(_formKey.currentState.validate()) {
|
||||
_flag = flag;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Custom Pause Duration',
|
||||
buttons: [
|
||||
LSDialog.button(
|
||||
text: 'Pause',
|
||||
onPressed: () => _setValues(true),
|
||||
),
|
||||
],
|
||||
content: [
|
||||
LSDialog.richText(
|
||||
children: [
|
||||
LSDialog.textSpanContent(text: 'Please enter how long in '),
|
||||
LSDialog.bolded(text: 'minutes'),
|
||||
LSDialog.textSpanContent(text: ' you want to pause the queue for.'),
|
||||
],
|
||||
alignment: TextAlign.center,
|
||||
),
|
||||
Form(
|
||||
key: _formKey,
|
||||
child: LSDialog.textFormInput(
|
||||
controller: _textController,
|
||||
title: 'Pause Duration in Minutes',
|
||||
keyboardType: TextInputType.number,
|
||||
onSubmitted: (_) => _setValues(true),
|
||||
validator: (value) {
|
||||
int _value = int.tryParse(value);
|
||||
if(_value == null || _value < 1) {
|
||||
return 'Must be at least 1';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
),
|
||||
),
|
||||
],
|
||||
contentPadding: LSDialog.inputTextDialogContentPadding(),
|
||||
);
|
||||
return [_flag, int.tryParse(_textController.text)];
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,7 @@ class _State extends State<NZBGetHistoryDetails> {
|
||||
title: LSTitle(text: 'Storage Location'),
|
||||
subtitle: LSSubtitle(text: _arguments.data.storageLocation),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
onTap: () async => LSDialogSystem.textPreview(context, 'Storage Location', _arguments.data.storageLocation),
|
||||
onTap: () async => GlobalDialogs.textPreview(context, 'Storage Location', _arguments.data.storageLocation),
|
||||
),
|
||||
LSDivider(),
|
||||
LSButton(
|
||||
@@ -126,7 +126,7 @@ class _State extends State<NZBGetHistoryDetails> {
|
||||
);
|
||||
|
||||
Future<void> _delete() async {
|
||||
List<dynamic> values = await LSDialogNZBGet.showDeleteHistoryPrompt(context);
|
||||
List<dynamic> values = await NZBGetDialogs.deleteHistory(context);
|
||||
if(values[0]) await NZBGetAPI.from(Database.currentProfileObject).deleteHistoryEntry(
|
||||
_arguments.data.id,
|
||||
hide: values[1],
|
||||
|
||||
@@ -29,18 +29,30 @@ class _State extends State<NZBGet> {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => ValueListenableBuilder(
|
||||
valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]),
|
||||
builder: (context, box, widget) {
|
||||
if(_profileState != Database.currentProfileObject.toString()) _refreshProfile();
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
body: _body,
|
||||
drawer: _drawer,
|
||||
appBar: _appBar,
|
||||
bottomNavigationBar: _bottomNavigationBar,
|
||||
);
|
||||
Widget build(BuildContext context) => WillPopScope(
|
||||
onWillPop: () async {
|
||||
if(_scaffoldKey.currentState.isDrawerOpen) {
|
||||
//If the drawer is open, return true to close it
|
||||
return true;
|
||||
} else {
|
||||
//If the drawer isn't open, open the drawer
|
||||
_scaffoldKey.currentState.openDrawer();
|
||||
return false;
|
||||
}
|
||||
},
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]),
|
||||
builder: (context, box, widget) {
|
||||
if(_profileState != Database.currentProfileObject.toString()) _refreshProfile();
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
body: _body,
|
||||
drawer: _drawer,
|
||||
appBar: _appBar,
|
||||
bottomNavigationBar: _bottomNavigationBar,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
Widget get _drawer => LSDrawer(page: 'nzbget');
|
||||
@@ -82,7 +94,7 @@ class _State extends State<NZBGet> {
|
||||
);
|
||||
|
||||
Future<void> _handlePopup() async {
|
||||
List<dynamic> values = await LSDialogNZBGet.showSettingsPrompt(context);
|
||||
List<dynamic> values = await NZBGetDialogs.globalSettings(context);
|
||||
if(values[0]) switch(values[1]) {
|
||||
case 'web_gui': _api.host.lsLinks_OpenLink(); break;
|
||||
case 'add_nzb': _addNZB(); break;
|
||||
@@ -93,7 +105,7 @@ class _State extends State<NZBGet> {
|
||||
}
|
||||
|
||||
Future<void> _addNZB() async {
|
||||
List values = await LSDialogNZBGet.showAddNZBPrompt(context);
|
||||
List values = await NZBGetDialogs.addNZB(context);
|
||||
if(values[0]) switch(values[1]) {
|
||||
case 'link': _addByURL(); break;
|
||||
case 'file': _addByFile(); break;
|
||||
@@ -102,7 +114,7 @@ class _State extends State<NZBGet> {
|
||||
}
|
||||
|
||||
Future<void> _addByURL() async {
|
||||
List values = await LSDialogNZBGet.showaddURLPrompt(context);
|
||||
List values = await NZBGetDialogs.addNZBUrl(context);
|
||||
if(values[0]) await _api.uploadURL(values[1])
|
||||
.then((_) => LSSnackBar(
|
||||
context: context,
|
||||
@@ -159,7 +171,7 @@ class _State extends State<NZBGet> {
|
||||
}
|
||||
|
||||
Future<void> _sort() async {
|
||||
List values = await LSDialogNZBGet.showSortPrompt(context);
|
||||
List values = await NZBGetDialogs.sortQueue(context);
|
||||
if(values[0]) await _api.sortQueue(values[1])
|
||||
.then((_) {
|
||||
_refreshKeys[0]?.currentState?.show();
|
||||
|
||||
@@ -50,10 +50,10 @@ class NZBGetAppBarStats extends StatelessWidget {
|
||||
: speed;
|
||||
|
||||
Future<void> _onTap(BuildContext context, String speed) async {
|
||||
List values = await LSDialogNZBGet.showSpeedPrompt(context, speed);
|
||||
List values = await NZBGetDialogs.speedLimit(context, speed);
|
||||
if(values[0]) switch(values[1]) {
|
||||
case -1: {
|
||||
values = await LSDialogNZBGet.showCustomSpeedPrompt(context);
|
||||
values = await NZBGetDialogs.customSpeedLimit(context);
|
||||
if(values[0]) NZBGetAPI.from(Database.currentProfileObject).setSpeedLimit(values[1])
|
||||
.then((_) => LSSnackBar(
|
||||
context: context,
|
||||
|
||||
@@ -56,7 +56,7 @@ class NZBGetHistoryTile extends StatelessWidget {
|
||||
}
|
||||
|
||||
Future<void> _handlePopup(BuildContext context) async {
|
||||
List values = await LSDialogNZBGet.showHistorySettingsPrompt(context, data.name);
|
||||
List values = await NZBGetDialogs.historySettings(context, data.name);
|
||||
if(values[0]) switch(values[1]) {
|
||||
case 'retry': {
|
||||
await NZBGetAPI.from(Database.currentProfileObject).retryHistoryEntry(data.id)
|
||||
|
||||
@@ -14,6 +14,6 @@ class NZBGetLogTile extends StatelessWidget {
|
||||
title: LSTitle(text: data.text),
|
||||
subtitle: LSSubtitle(text: data.timestamp),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
onTap: () async => LSDialogSystem.textPreview(context, 'Log Entry', data.text),
|
||||
onTap: () async => GlobalDialogs.textPreview(context, 'Log Entry', data.text),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,10 +27,10 @@ class NZBGetQueueFAB extends StatelessWidget {
|
||||
}
|
||||
|
||||
Future<void> _toggleFor(BuildContext context) async {
|
||||
List values = await LSDialogNZBGet.showPauseForPrompt(context);
|
||||
List values = await NZBGetDialogs.pauseFor(context);
|
||||
if(values[0]) {
|
||||
if(values[1] == -1) {
|
||||
List values = await LSDialogNZBGet.showCustomPauseForPrompt(context);
|
||||
List values = await NZBGetDialogs.customPauseFor(context);
|
||||
if(values[0]) await NZBGetAPI.from(Database.currentProfileObject).pauseQueueFor(values[1])
|
||||
.then((_) => LSSnackBar(
|
||||
context: context,
|
||||
|
||||
@@ -58,7 +58,7 @@ class _State extends State<NZBGetQueueTile> {
|
||||
|
||||
Future<void> _handlePopup() async {
|
||||
_Helper _helper = _Helper(widget.queueContext, widget.data, widget.snackbar, widget.refresh);
|
||||
List values = await LSDialogNZBGet.showQueueSettingsPrompt(widget.queueContext, widget.data.name, widget.data.paused);
|
||||
List values = await NZBGetDialogs.queueSettings(widget.queueContext, widget.data.name, widget.data.paused);
|
||||
if(values[0]) switch(values[1]) {
|
||||
case 'status': widget.data.paused
|
||||
? _helper._resumeJob()
|
||||
@@ -123,7 +123,7 @@ class _Helper {
|
||||
|
||||
Future<void> _category() async {
|
||||
List<NZBGetCategoryData> categories = await NZBGetAPI.from(Database.currentProfileObject).getCategories();
|
||||
List values = await LSDialogNZBGet.showCategoryPrompt(context, categories);
|
||||
List values = await NZBGetDialogs.changeCategory(context, categories);
|
||||
if(values[0]) await NZBGetAPI.from(Database.currentProfileObject).setJobCategory(data.id, values[1])
|
||||
.then((_) {
|
||||
snackbar(
|
||||
@@ -141,7 +141,7 @@ class _Helper {
|
||||
}
|
||||
|
||||
Future<void> _priority() async {
|
||||
List values = await LSDialogNZBGet.showChangePriorityPrompt(context);
|
||||
List values = await NZBGetDialogs.changePriority(context);
|
||||
if(values[0]) await NZBGetAPI.from(Database.currentProfileObject).setJobPriority(data.id, values[1])
|
||||
.then((_) {
|
||||
snackbar(
|
||||
@@ -159,7 +159,7 @@ class _Helper {
|
||||
}
|
||||
|
||||
Future<void> _rename() async {
|
||||
List values = await LSDialogNZBGet.showRenameJobPrompt(context, data.name);
|
||||
List values = await NZBGetDialogs.renameJob(context, data.name);
|
||||
if(values[0]) NZBGetAPI.from(Database.currentProfileObject).renameJob(data.id, values[1])
|
||||
.then((_) {
|
||||
snackbar(
|
||||
@@ -177,7 +177,7 @@ class _Helper {
|
||||
}
|
||||
|
||||
Future<void> _delete() async {
|
||||
List values = await LSDialogNZBGet.showDeleteJobPrompt(context);
|
||||
List values = await NZBGetDialogs.deleteJob(context);
|
||||
if(values[0]) await NZBGetAPI.from(Database.currentProfileObject).deleteJob(data.id)
|
||||
.then((_) {
|
||||
snackbar(
|
||||
@@ -195,7 +195,7 @@ class _Helper {
|
||||
}
|
||||
|
||||
Future<void> _password() async {
|
||||
List values = await LSDialogNZBGet.showSetPasswordPrompt(context);
|
||||
List values = await NZBGetDialogs.setPassword(context);
|
||||
if(values[0]) await NZBGetAPI.from(Database.currentProfileObject).setJobPassword(data.id, values[1])
|
||||
.then((_) {
|
||||
snackbar(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export './core/api.dart';
|
||||
export './core/constants.dart';
|
||||
export './core/database.dart';
|
||||
export './core/dialogs.dart';
|
||||
export './core/state.dart';
|
||||
|
||||
@@ -11,12 +11,14 @@ class RadarrAPI extends API {
|
||||
|
||||
RadarrAPI._internal(this._values, this._dio);
|
||||
factory RadarrAPI.from(ProfileHiveObject profile) {
|
||||
Map<String, dynamic> _headers = Map<String, dynamic>.from(profile.getRadarr()['headers']);
|
||||
Dio _client = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: '${profile.getRadarr()['host']}/api/',
|
||||
queryParameters: {
|
||||
'apikey': profile.getRadarr()['key'],
|
||||
if(profile.getRadarr()['key'] != '') 'apikey': profile.getRadarr()['key'],
|
||||
},
|
||||
headers: _headers,
|
||||
followRedirects: true,
|
||||
maxRedirects: 5,
|
||||
),
|
||||
|
||||
@@ -4,7 +4,9 @@ import 'package:lunasea/modules/radarr.dart';
|
||||
class RadarrConstants {
|
||||
RadarrConstants._();
|
||||
|
||||
static const Map SERVICE_MAP = {
|
||||
static const String MODULE_KEY = 'radarr';
|
||||
|
||||
static const Map MODULE_MAP = {
|
||||
'name': 'Radarr',
|
||||
'desc': 'Manage Movies',
|
||||
'icon': CustomIcons.movies,
|
||||
|
||||
269
lib/modules/radarr/core/dialogs.dart
Normal file
269
lib/modules/radarr/core/dialogs.dart
Normal file
@@ -0,0 +1,269 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
import 'package:lunasea/modules/radarr.dart';
|
||||
|
||||
class RadarrDialogs {
|
||||
RadarrDialogs._();
|
||||
|
||||
static Future<List<dynamic>> deleteMovieFile(BuildContext context) async {
|
||||
bool _flag = false;
|
||||
|
||||
void _setValues(bool flag) {
|
||||
_flag = flag;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Delete Movie File',
|
||||
buttons: [
|
||||
LSDialog.button(
|
||||
text: 'Delete',
|
||||
textColor: LSColors.red,
|
||||
onPressed: () => _setValues(true),
|
||||
),
|
||||
],
|
||||
content: [
|
||||
LSDialog.textContent(text: 'Are you sure you want to delete this movie file?'),
|
||||
],
|
||||
contentPadding: LSDialog.textDialogContentPadding(),
|
||||
);
|
||||
return [_flag];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> deleteMovie(BuildContext context) async {
|
||||
bool _flag = false;
|
||||
bool _files = false;
|
||||
|
||||
void _setValues(bool flag, bool files) {
|
||||
_flag = flag;
|
||||
_files = files;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Remove Movie',
|
||||
buttons: [
|
||||
LSDialog.button(
|
||||
text: 'Remove + Files',
|
||||
textColor: LSColors.red,
|
||||
onPressed: () => _setValues(true, true),
|
||||
),
|
||||
LSDialog.button(
|
||||
text: 'Remove',
|
||||
textColor: LSColors.red,
|
||||
onPressed: () => _setValues(true, false),
|
||||
),
|
||||
],
|
||||
content: [
|
||||
LSDialog.textContent(text: 'Are you sure you want to remove the movie from Radarr?'),
|
||||
],
|
||||
contentPadding: LSDialog.textDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _files];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> searchAllMissing(BuildContext context) async {
|
||||
bool _flag = false;
|
||||
|
||||
void _setValues(bool flag) {
|
||||
_flag = flag;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Search All Missing',
|
||||
buttons: [
|
||||
LSDialog.button(
|
||||
text: 'Search',
|
||||
textColor: LSColors.accent,
|
||||
onPressed: () => _setValues(true),
|
||||
),
|
||||
],
|
||||
content: [
|
||||
LSDialog.textContent(text: 'Are you sure you want to search for all missing movies?'),
|
||||
],
|
||||
contentPadding: LSDialog.textDialogContentPadding(),
|
||||
);
|
||||
return [_flag];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> editMovie(BuildContext context, RadarrCatalogueData entry) async {
|
||||
List<List<dynamic>> _options = [
|
||||
['Edit Movie', Icons.edit, 'edit_movie'],
|
||||
['Refresh Movie', Icons.refresh, 'refresh_movie'],
|
||||
['Remove Movie', Icons.delete, 'remove_movie'],
|
||||
];
|
||||
bool _flag = false;
|
||||
String _value = '';
|
||||
|
||||
void _setValues(bool flag, String value) {
|
||||
_flag = flag;
|
||||
_value = value;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: entry.title,
|
||||
content: List.generate(
|
||||
_options.length,
|
||||
(index) => LSDialog.tile(
|
||||
text: _options[index][0],
|
||||
icon: _options[index][1],
|
||||
iconColor: LSColors.list(index),
|
||||
onTap: () => _setValues(true, _options[index][2]),
|
||||
),
|
||||
),
|
||||
contentPadding: LSDialog.listDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _value];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> editMinimumAvailability(BuildContext context, List<RadarrAvailability> availability) async {
|
||||
bool _flag = false;
|
||||
RadarrAvailability _entry;
|
||||
|
||||
void _setValues(bool flag, RadarrAvailability entry) {
|
||||
_flag = flag;
|
||||
_entry = entry;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Minimum Availability',
|
||||
content: List.generate(
|
||||
availability.length,
|
||||
(index) => LSDialog.tile(
|
||||
text: availability[index].name,
|
||||
icon: Icons.folder,
|
||||
iconColor: LSColors.list(index),
|
||||
onTap: () => _setValues(true, availability[index]),
|
||||
),
|
||||
),
|
||||
contentPadding: LSDialog.listDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _entry];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> editRootFolder(BuildContext context, List<RadarrRootFolder> folders) async {
|
||||
bool _flag = false;
|
||||
RadarrRootFolder _folder;
|
||||
|
||||
void _setValues(bool flag, RadarrRootFolder folder) {
|
||||
_flag = flag;
|
||||
_folder = folder;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Root Folder',
|
||||
content: List.generate(
|
||||
folders.length,
|
||||
(index) => LSDialog.tile(
|
||||
text: folders[index].path,
|
||||
subtitle: LSDialog.richText(
|
||||
children: [
|
||||
LSDialog.bolded(text: folders[index].freeSpace.lsBytes_BytesToString()),
|
||||
],
|
||||
),
|
||||
icon: Icons.folder,
|
||||
iconColor: LSColors.list(index),
|
||||
onTap: () => _setValues(true, folders[index]),
|
||||
),
|
||||
),
|
||||
contentPadding: LSDialog.listDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _folder];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> editQualityProfile(BuildContext context, List<RadarrQualityProfile> qualities) async {
|
||||
bool _flag = false;
|
||||
RadarrQualityProfile _quality;
|
||||
|
||||
void _setValues(bool flag, RadarrQualityProfile quality) {
|
||||
_flag = flag;
|
||||
_quality = quality;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Quality Profile',
|
||||
content: List.generate(
|
||||
qualities.length,
|
||||
(index) => LSDialog.tile(
|
||||
text: qualities[index].name,
|
||||
icon: Icons.portrait,
|
||||
iconColor: LSColors.list(index),
|
||||
onTap: () => _setValues(true, qualities[index]),
|
||||
),
|
||||
),
|
||||
contentPadding: LSDialog.listDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _quality];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> downloadWarning(BuildContext context) async {
|
||||
bool _flag = false;
|
||||
|
||||
void _setValues(bool flag) {
|
||||
_flag = flag;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Download Release',
|
||||
buttons: [
|
||||
LSDialog.button(
|
||||
text: 'Download',
|
||||
onPressed: () => _setValues(true),
|
||||
),
|
||||
],
|
||||
content: [
|
||||
LSDialog.textContent(text: 'Are you sure you want to download this release? It has been marked as a rejected release by Radarr.')
|
||||
],
|
||||
contentPadding: LSDialog.textDialogContentPadding(),
|
||||
);
|
||||
return [_flag];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> globalSettings(BuildContext context) async {
|
||||
List<List<dynamic>> _options = [
|
||||
['View Web GUI', Icons.language, 'web_gui'],
|
||||
['Update Library', Icons.autorenew, 'update_library'],
|
||||
['Run RSS Sync', Icons.rss_feed, 'rss_sync'],
|
||||
['Search All Missing', Icons.search, 'missing_search'],
|
||||
['Backup Database', Icons.save, 'backup'],
|
||||
];
|
||||
bool _flag = false;
|
||||
String _value = '';
|
||||
|
||||
void _setValues(bool flag, String value) {
|
||||
_flag = flag;
|
||||
_value = value;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
await LSDialog.dialog(
|
||||
context: context,
|
||||
title: 'Radarr Settings',
|
||||
content: List.generate(
|
||||
_options.length,
|
||||
(index) => LSDialog.tile(
|
||||
text: _options[index][0],
|
||||
icon: _options[index][1],
|
||||
iconColor: LSColors.list(index),
|
||||
onTap: () => _setValues(true, _options[index][2]),
|
||||
),
|
||||
),
|
||||
contentPadding: LSDialog.listDialogContentPadding(),
|
||||
);
|
||||
return [_flag, _value];
|
||||
}
|
||||
}
|
||||
@@ -48,4 +48,12 @@ class RadarrModel extends ChangeNotifier {
|
||||
_navigationIndex = navigationIndex;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
int _movieNavigationIndex = 0;
|
||||
int get movieNavigationIndex => _movieNavigationIndex;
|
||||
set movieNavigationIndex(int movieNavigationIndex) {
|
||||
assert(movieNavigationIndex != null);
|
||||
_movieNavigationIndex = movieNavigationIndex;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +138,7 @@ class _State extends State<RadarrAddDetails> {
|
||||
: _arguments.data.overview,
|
||||
uri: _arguments.data.posterURI ?? '',
|
||||
fallbackImage: 'assets/images/radarr/nomovieposter.png',
|
||||
headers: Database.currentProfileObject.getRadarr()['headers'],
|
||||
),
|
||||
LSDivider(),
|
||||
ValueListenableBuilder(
|
||||
@@ -162,7 +163,7 @@ class _State extends State<RadarrAddDetails> {
|
||||
subtitle: LSSubtitle(text: _rootfolder?.path ?? 'Unknown Root Folder'),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
onTap: () async {
|
||||
List _values = await LSDialogRadarr.showEditRootFolderPrompt(context, _rootFolders);
|
||||
List _values = await RadarrDialogs.editRootFolder(context, _rootFolders);
|
||||
if(_values[0]) box.put(RadarrDatabaseValue.ADD_ROOT_FOLDER.key, _values[1]);
|
||||
},
|
||||
);
|
||||
@@ -177,7 +178,7 @@ class _State extends State<RadarrAddDetails> {
|
||||
subtitle: LSSubtitle(text: _profile?.name ?? 'Unknown Profile'),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
onTap: () async {
|
||||
List _values = await LSDialogRadarr.showEditQualityProfilePrompt(context, _qualityProfiles);
|
||||
List _values = await RadarrDialogs.editQualityProfile(context, _qualityProfiles);
|
||||
if(_values[0]) box.put(RadarrDatabaseValue.ADD_QUALITY_PROFILE.key, _values[1]);
|
||||
},
|
||||
);
|
||||
@@ -192,7 +193,7 @@ class _State extends State<RadarrAddDetails> {
|
||||
subtitle: LSSubtitle(text: _availability?.name ?? 'Unknown Availability'),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
onTap: () async {
|
||||
List _values = await LSDialogRadarr.showMinimumAvailabilityPrompt(context, RadarrConstants.MINIMUM_AVAILBILITIES);
|
||||
List _values = await RadarrDialogs.editMinimumAvailability(context, RadarrConstants.MINIMUM_AVAILBILITIES);
|
||||
if(_values[0]) box.put(RadarrDatabaseValue.ADD_AVAILABILITY.key, _values[1]);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -22,20 +22,16 @@ class RadarrDetailsMovie extends StatefulWidget {
|
||||
|
||||
class _State extends State<RadarrDetailsMovie> {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
final _pageController = PageController(initialPage: 1);
|
||||
RadarrDetailsMovieArguments _arguments;
|
||||
bool _error = false;
|
||||
|
||||
final List<String> _tabTitles = [
|
||||
'Overview',
|
||||
'Search',
|
||||
'Files',
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
_arguments = ModalRoute.of(context).settings.arguments;
|
||||
Provider.of<RadarrModel>(context, listen: false).movieNavigationIndex = 1;
|
||||
_fetch();
|
||||
});
|
||||
}
|
||||
@@ -53,7 +49,10 @@ class _State extends State<RadarrDetailsMovie> {
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: _arguments == null || _arguments.data == null ? _appBar : null,
|
||||
appBar: _appBar,
|
||||
bottomNavigationBar: _arguments != null && _arguments.data != null
|
||||
? _bottomNavigationBar
|
||||
: null,
|
||||
body: _arguments != null
|
||||
? _arguments.data != null
|
||||
? _body
|
||||
@@ -63,36 +62,35 @@ class _State extends State<RadarrDetailsMovie> {
|
||||
: null,
|
||||
);
|
||||
|
||||
Widget get _appBar => LSAppBar(title: 'Movie Details');
|
||||
|
||||
Widget get _body => DefaultTabController(
|
||||
length: _tabTitles.length,
|
||||
initialIndex: 1,
|
||||
child: LSSliverAppBarTabs(
|
||||
title: _arguments.data.title,
|
||||
backgroundURI: _arguments.data.fanartURI(highRes: true),
|
||||
body: TabBarView(
|
||||
children: <Widget>[
|
||||
RadarrDetailsOverview(data: _arguments.data),
|
||||
RadarrDetailsSearch(data: _arguments.data),
|
||||
RadarrDetailsFiles(data: _arguments.data),
|
||||
],
|
||||
),
|
||||
bottom: TabBar(
|
||||
tabs: <Widget>[
|
||||
for(int i =0; i<_tabTitles.length; i++) Tab(
|
||||
child: Text(_tabTitles[i]),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: <Widget>[
|
||||
Widget get _appBar => LSAppBar(
|
||||
title: _arguments == null || _arguments.data == null
|
||||
? 'Movie Details'
|
||||
: _arguments.data.title,
|
||||
actions: _arguments == null || _arguments.data == null
|
||||
? null
|
||||
: <Widget>[
|
||||
RadarrDetailsEditButton(
|
||||
data: _arguments.data,
|
||||
remove: (bool withData) => _removeCallback(withData),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Widget get _bottomNavigationBar => RadarrMovieNavigationBar(pageController: _pageController);
|
||||
|
||||
List<Widget> get _tabs => [
|
||||
RadarrDetailsOverview(data: _arguments.data),
|
||||
RadarrDetailsSearch(data: _arguments.data),
|
||||
RadarrDetailsFiles(data: _arguments.data),
|
||||
];
|
||||
|
||||
Widget get _body => PageView(
|
||||
controller: _pageController,
|
||||
children: _tabs,
|
||||
onPageChanged: _onPageChanged,
|
||||
);
|
||||
|
||||
void _onPageChanged(int index) => Provider.of<RadarrModel>(context, listen: false).movieNavigationIndex = index;
|
||||
|
||||
Future<void> _removeCallback(bool withData) async => Navigator.of(context).pop(['remove_movie', withData]);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ class _State extends State<RadarrDetailsSearchResults> {
|
||||
title: LSTitle(text: 'Release Title'),
|
||||
subtitle: LSSubtitle(text: _arguments.data.title),
|
||||
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
|
||||
onTap: () async => LSDialogSystem.textPreview(context, 'Release Title', _arguments.data.title),
|
||||
onTap: () async => GlobalDialogs.textPreview(context, 'Release Title', _arguments.data.title),
|
||||
),
|
||||
LSContainerRow(
|
||||
children: <Widget>[
|
||||
@@ -156,42 +156,22 @@ class _State extends State<RadarrDetailsSearchResults> {
|
||||
);
|
||||
|
||||
Future<void> _download() async {
|
||||
if(_arguments.data.approved) {
|
||||
await _startDownload()
|
||||
.then((_) => LSSnackBar(
|
||||
context: context,
|
||||
title: 'Downloading...',
|
||||
message: _arguments.data.title,
|
||||
type: SNACKBAR_TYPE.success,
|
||||
showButton: true,
|
||||
buttonText: 'Back',
|
||||
buttonOnPressed: () => Navigator.of(context).popUntil(ModalRoute.withName(Radarr.ROUTE_NAME)),
|
||||
))
|
||||
.catchError((_) => LSSnackBar(
|
||||
context: context,
|
||||
title: 'Failed to Start Downloading',
|
||||
message: Constants.CHECK_LOGS_MESSAGE,
|
||||
type: SNACKBAR_TYPE.failure,
|
||||
));
|
||||
} else {
|
||||
List<dynamic> values = await LSDialogRadarr.showDownloadWarningPrompt(context);
|
||||
if(values[0]) await _startDownload()
|
||||
.then((_) => LSSnackBar(
|
||||
context: context,
|
||||
title: 'Downloading...',
|
||||
message: _arguments.data.title,
|
||||
type: SNACKBAR_TYPE.success,
|
||||
showButton: true,
|
||||
buttonText: 'Back',
|
||||
buttonOnPressed: () => Navigator.of(context).popUntil(ModalRoute.withName(Radarr.ROUTE_NAME)),
|
||||
))
|
||||
.catchError((_) => LSSnackBar(
|
||||
context: context,
|
||||
title: 'Failed to Start Downloading',
|
||||
message: Constants.CHECK_LOGS_MESSAGE,
|
||||
type: SNACKBAR_TYPE.failure,
|
||||
));
|
||||
}
|
||||
await _startDownload()
|
||||
.then((_) => LSSnackBar(
|
||||
context: context,
|
||||
title: 'Downloading...',
|
||||
message: _arguments.data.title,
|
||||
type: SNACKBAR_TYPE.success,
|
||||
showButton: true,
|
||||
buttonText: 'Back',
|
||||
buttonOnPressed: () => Navigator.of(context).popUntil(ModalRoute.withName(Radarr.ROUTE_NAME)),
|
||||
))
|
||||
.catchError((_) => LSSnackBar(
|
||||
context: context,
|
||||
title: 'Failed to Start Downloading',
|
||||
message: Constants.CHECK_LOGS_MESSAGE,
|
||||
type: SNACKBAR_TYPE.failure,
|
||||
));
|
||||
}
|
||||
|
||||
Future<bool> _startDownload() async {
|
||||
@@ -204,6 +184,6 @@ class _State extends State<RadarrDetailsSearchResults> {
|
||||
for(var i=0; i<_arguments.data.rejections.length; i++) {
|
||||
reject += '${i+1}. ${_arguments.data.rejections[i]}\n';
|
||||
}
|
||||
await LSDialogSystem.textPreview(context, 'Rejection Reasons', reject.substring(0, reject.length-1));
|
||||
await GlobalDialogs.textPreview(context, 'Rejection Reasons', reject.substring(0, reject.length-1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,17 +140,17 @@ class _State extends State<RadarrEditMovie> {
|
||||
);
|
||||
|
||||
Future<void> _changePath() async {
|
||||
List<dynamic> _values = await LSDialogSystem.editText(context, 'Movie Path', prefill: _path);
|
||||
List<dynamic> _values = await GlobalDialogs.editText(context, 'Movie Path', prefill: _path);
|
||||
if(_values[0] && mounted) setState(() => _path = _values[1]);
|
||||
}
|
||||
|
||||
Future<void> _changeProfile() async {
|
||||
List<dynamic> _values = await LSDialogRadarr.showEditQualityProfilePrompt(context, _qualityProfiles);
|
||||
List<dynamic> _values = await RadarrDialogs.editQualityProfile(context, _qualityProfiles);
|
||||
if(_values[0] && mounted) setState(() => _qualityProfile = _values[1]);
|
||||
}
|
||||
|
||||
Future<void> _changeMinimumAvailability() async {
|
||||
List<dynamic> _values = await LSDialogRadarr.showMinimumAvailabilityPrompt(context, RadarrConstants.MINIMUM_AVAILBILITIES);
|
||||
List<dynamic> _values = await RadarrDialogs.editMinimumAvailability(context, RadarrConstants.MINIMUM_AVAILBILITIES);
|
||||
if(_values[0] && mounted) setState(() => _minimumAvailability = _values[1]);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,18 +29,30 @@ class _State extends State<Radarr> {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => ValueListenableBuilder(
|
||||
valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]),
|
||||
builder: (context, box, widget) {
|
||||
if(_profileState != Database.currentProfileObject.toString()) _refreshProfile();
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
body: _body,
|
||||
drawer: _drawer,
|
||||
appBar: _appBar,
|
||||
bottomNavigationBar: _bottomNavigationBar,
|
||||
);
|
||||
Widget build(BuildContext context) => WillPopScope(
|
||||
onWillPop: () async {
|
||||
if(_scaffoldKey.currentState.isDrawerOpen) {
|
||||
//If the drawer is open, return true to close it
|
||||
return true;
|
||||
} else {
|
||||
//If the drawer isn't open, open the drawer
|
||||
_scaffoldKey.currentState.openDrawer();
|
||||
return false;
|
||||
}
|
||||
},
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]),
|
||||
builder: (context, box, widget) {
|
||||
if(_profileState != Database.currentProfileObject.toString()) _refreshProfile();
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
body: _body,
|
||||
drawer: _drawer,
|
||||
appBar: _appBar,
|
||||
bottomNavigationBar: _bottomNavigationBar,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
Widget get _drawer => LSDrawer(page: 'radarr');
|
||||
@@ -108,7 +120,7 @@ class _State extends State<Radarr> {
|
||||
}
|
||||
|
||||
Future<void> _handlePopup() async {
|
||||
List<dynamic> values = await LSDialogRadarr.showSettingsPrompt(context);
|
||||
List<dynamic> values = await RadarrDialogs.globalSettings(context);
|
||||
if(values[0]) switch(values[1]) {
|
||||
case 'web_gui': await _api.host?.toString()?.lsLinks_OpenLink(); break;
|
||||
case 'update_library': await _api.updateLibrary()
|
||||
@@ -124,7 +136,7 @@ class _State extends State<Radarr> {
|
||||
.catchError((_) => LSSnackBar(context: context, title: 'Failed to Backup Database', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure));
|
||||
break;
|
||||
case 'missing_search': {
|
||||
List<dynamic> values = await LSDialogRadarr.showSearchMissingPrompt(context);
|
||||
List<dynamic> values = await RadarrDialogs.searchAllMissing(context);
|
||||
if(values[0]) await _api.searchAllMissing()
|
||||
.then((_) => LSSnackBar(context: context, title: 'Searching...', message: 'Search for all missing movies'))
|
||||
.catchError((_) => LSSnackBar(context: context, title: 'Failed to Search', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure));
|
||||
|
||||
@@ -14,3 +14,4 @@ export './widgets/details_overview.dart';
|
||||
export './widgets/add_search_bar.dart';
|
||||
export './widgets/add_search_result_tile.dart';
|
||||
export './widgets/navigation_bar.dart';
|
||||
export './widgets/movie_navigation_bar.dart';
|
||||
|
||||
@@ -87,6 +87,7 @@ class _State extends State<RadarrCatalogueTile> {
|
||||
decoration: LSCardBackground(
|
||||
uri: widget.data.posterURI(),
|
||||
darken: !widget.data.monitored,
|
||||
headers: Database.currentProfileObject.getRadarr()['headers'],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -137,7 +138,7 @@ class _State extends State<RadarrCatalogueTile> {
|
||||
}
|
||||
|
||||
Future<void> _handlePopup() async {
|
||||
List<dynamic> values = await LSDialogRadarr.showEditMoviePrompt(context, widget.data);
|
||||
List<dynamic> values = await RadarrDialogs.editMovie(context, widget.data);
|
||||
if(values[0]) switch(values[1]) {
|
||||
case 'refresh_movie': _refreshMovie(); break;
|
||||
case 'edit_movie': _editMovie(); break;
|
||||
@@ -170,10 +171,10 @@ class _State extends State<RadarrCatalogueTile> {
|
||||
|
||||
Future<void> _removeMovie() async {
|
||||
final _api = RadarrAPI.from(Database.currentProfileObject);
|
||||
List values = await LSDialogRadarr.showDeleteMoviePrompt(context);
|
||||
List values = await RadarrDialogs.deleteMovie(context);
|
||||
if(values[0]) {
|
||||
if(values[1]) {
|
||||
values = await LSDialogSystem.deleteCatalogueWithFiles(context, widget.data.title);
|
||||
values = await GlobalDialogs.deleteCatalogueWithFiles(context, widget.data.title);
|
||||
if(values[0]) {
|
||||
await _api.removeMovie(widget.data.movieID, deleteFiles: true)
|
||||
.then((_) {
|
||||
|
||||
@@ -25,7 +25,7 @@ class _State extends State<RadarrDetailsEditButton> {
|
||||
);
|
||||
|
||||
Future<void> _handlePopup(BuildContext context) async {
|
||||
List<dynamic> values = await LSDialogRadarr.showEditMoviePrompt(context, widget.data);
|
||||
List<dynamic> values = await RadarrDialogs.editMovie(context, widget.data);
|
||||
if(values[0]) switch(values[1]) {
|
||||
case 'refresh_movie': _refreshMovie(context); break;
|
||||
case 'edit_movie': _editMovie(context); break;
|
||||
@@ -56,10 +56,10 @@ class _State extends State<RadarrDetailsEditButton> {
|
||||
|
||||
Future<void> _removeMovie(BuildContext context) async {
|
||||
final _api = RadarrAPI.from(Database.currentProfileObject);
|
||||
List values = await LSDialogRadarr.showDeleteMoviePrompt(context);
|
||||
List values = await RadarrDialogs.deleteMovie(context);
|
||||
if(values[0]) {
|
||||
if(values[1]) {
|
||||
values = await LSDialogSystem.deleteCatalogueWithFiles(context, widget.data.title);
|
||||
values = await GlobalDialogs.deleteCatalogueWithFiles(context, widget.data.title);
|
||||
if(values[0]) {
|
||||
await _api.removeMovie(widget.data.movieID, deleteFiles: true)
|
||||
.then((_) => widget.remove(true))
|
||||
|
||||
@@ -46,6 +46,9 @@ class _State extends State<RadarrDetailsFiles> with AutomaticKeepAliveClientMixi
|
||||
TextSpan(text: '${widget.data.movieFile['quality']['quality']['name'] ?? 'Unknown'}'),
|
||||
],
|
||||
),
|
||||
overflow: TextOverflow.fade,
|
||||
maxLines: 1,
|
||||
softWrap: false,
|
||||
),
|
||||
trailing: LSIconButton(
|
||||
icon: Icons.delete,
|
||||
@@ -57,7 +60,7 @@ class _State extends State<RadarrDetailsFiles> with AutomaticKeepAliveClientMixi
|
||||
Widget get _empty => LSGenericMessage(text: 'No Files Found');
|
||||
|
||||
Future<void> _delete() async {
|
||||
List<dynamic> values = await LSDialogRadarr.showDeleteMovieFilePrompt(context);
|
||||
List<dynamic> values = await RadarrDialogs.deleteMovieFile(context);
|
||||
RadarrAPI _api = RadarrAPI.from(Database.currentProfileObject);
|
||||
if(values[0]) _api.removeMovieFile(widget.data.movieFile['id'])
|
||||
.then((_) {
|
||||
|
||||
@@ -30,11 +30,12 @@ class _State extends State<RadarrDetailsOverview> with AutomaticKeepAliveClientM
|
||||
: widget?.data?.overview,
|
||||
uri: widget?.data?.posterURI() ?? '',
|
||||
fallbackImage: 'assets/images/radarr/nomovieposter.png',
|
||||
headers: Database.currentProfileObject.getRadarr()['headers'],
|
||||
),
|
||||
LSCardTile(
|
||||
title: LSTitle(text: 'Movie Path', centerText: true),
|
||||
subtitle: LSSubtitle(text: widget?.data?.path ?? 'Unknown', centerText: true),
|
||||
onTap: () => LSDialogSystem.textPreview(context, widget?.data?.title, widget?.data?.path ?? 'Unknown'),
|
||||
onTap: () => GlobalDialogs.textPreview(context, widget?.data?.title, widget?.data?.path ?? 'Unknown'),
|
||||
),
|
||||
LSContainerRow(
|
||||
children: <Widget>[
|
||||
|
||||
@@ -84,7 +84,7 @@ class RadarrDetailsSearchTile extends StatelessWidget {
|
||||
|
||||
Future<void> _trailingLongPressed(BuildContext context) async {
|
||||
if(!data.approved) {
|
||||
List<dynamic> values = await LSDialogRadarr.showDownloadWarningPrompt(context);
|
||||
List<dynamic> values = await RadarrDialogs.downloadWarning(context);
|
||||
if(values[0]) await _startDownload()
|
||||
.then((_) => LSSnackBar(
|
||||
context: context,
|
||||
@@ -114,6 +114,6 @@ class RadarrDetailsSearchTile extends StatelessWidget {
|
||||
for(var i=0; i<data.rejections.length; i++) {
|
||||
reject += '${i+1}. ${data.rejections[i]}\n';
|
||||
}
|
||||
await LSDialogSystem.textPreview(context, 'Rejection Reasons', reject.substring(0, reject.length-1));
|
||||
await GlobalDialogs.textPreview(context, 'Rejection Reasons', reject.substring(0, reject.length-1));
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,10 @@ class _State extends State<RadarrMissingTile> {
|
||||
onPressed: () async => _search(),
|
||||
),
|
||||
onTap: () async => _enterMovie(),
|
||||
decoration: LSCardBackground(uri: widget.data.posterURI()),
|
||||
decoration: LSCardBackground(
|
||||
uri: widget.data.posterURI(),
|
||||
headers: Database.currentProfileObject.getRadarr()['headers'],
|
||||
),
|
||||
padContent: true,
|
||||
);
|
||||
|
||||
|
||||
48
lib/modules/radarr/widgets/movie_navigation_bar.dart
Normal file
48
lib/modules/radarr/widgets/movie_navigation_bar.dart
Normal file
@@ -0,0 +1,48 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
import '../../radarr.dart';
|
||||
|
||||
class RadarrMovieNavigationBar extends StatefulWidget {
|
||||
final PageController pageController;
|
||||
|
||||
RadarrMovieNavigationBar({
|
||||
Key key,
|
||||
@required this.pageController,
|
||||
}): super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _State();
|
||||
}
|
||||
|
||||
class _State extends State<RadarrMovieNavigationBar> {
|
||||
static const List<String> _navbarTitles = [
|
||||
'Overview',
|
||||
'Search',
|
||||
'Files',
|
||||
];
|
||||
|
||||
static const List<IconData> _navbarIcons = [
|
||||
Icons.subject,
|
||||
CustomIcons.movies,
|
||||
Icons.insert_drive_file,
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Selector<RadarrModel, int>(
|
||||
selector: (_, model) => model.movieNavigationIndex,
|
||||
builder: (context, index, _) => LSBottomNavigationBar(
|
||||
index: index,
|
||||
icons: _navbarIcons,
|
||||
titles: _navbarTitles,
|
||||
onTap: (index) async => await _navOnTap(index),
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> _navOnTap(int index) async {
|
||||
await widget.pageController.animateToPage(
|
||||
index,
|
||||
duration: Duration(milliseconds: Constants.UI_NAVIGATION_SPEED),
|
||||
curve: Curves.easeOutSine,
|
||||
).then((_) => Provider.of<RadarrModel>(context, listen: false).movieNavigationIndex = index);
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,10 @@ class _State extends State<RadarrUpcomingTile> {
|
||||
),
|
||||
onTap: () async => _enterMovie(),
|
||||
padContent: true,
|
||||
decoration: LSCardBackground(uri: widget.data.posterURI()),
|
||||
decoration: LSCardBackground(
|
||||
uri: widget.data.posterURI(),
|
||||
headers: Database.currentProfileObject.getRadarr()['headers'],
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> _search() async {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export './sabnzbd/api.dart';
|
||||
export './sabnzbd/core.dart';
|
||||
export './sabnzbd/routes.dart';
|
||||
export './sabnzbd/state.dart';
|
||||
export './sabnzbd/widgets.dart';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user