diff --git a/.gitignore b/.gitignore index af5960df..24958ac0 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ LunaSea-armeabi-v7a-release.apk LunaSea-arm64-v8a-release.apk LunaSea-x86_64-release.apk ios/build/ +_changelog.txt # Build Configurations ExportOptions.plist diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 6c056e12..e8937bc0 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -620,4 +620,4 @@ /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; -} \ No newline at end of file +} diff --git a/lib/core.dart b/lib/core.dart index 9dbd85b8..0cbc0895 100644 --- a/lib/core.dart +++ b/lib/core.dart @@ -2,16 +2,20 @@ export 'core/constants.dart'; export 'core/configuration.dart'; export 'core/database.dart'; export 'core/dialogs.dart'; +export 'core/encryption.dart'; export 'core/extensions.dart'; -export 'core/homescreen_actions.dart'; +export 'core/filesystem.dart'; export 'core/image_cache.dart'; export 'core/in_app_purchases.dart'; export 'core/logger.dart'; +export 'core/luna_ui.dart'; export 'core/module_map.dart'; +export 'core/networking.dart'; +export 'core/profile.dart'; export 'core/providers.dart'; +export 'core/quick_actions.dart'; export 'core/router.dart'; -export 'core/state.dart'; export 'core/theme.dart'; export 'core/types.dart'; export 'core/ui.dart'; -export 'core/uuid.dart'; \ No newline at end of file +export 'core/uuid.dart'; diff --git a/lib/core/configuration.dart b/lib/core/configuration.dart index 3f21a26c..5e6a84be 100644 --- a/lib/core/configuration.dart +++ b/lib/core/configuration.dart @@ -1,4 +1,2 @@ export 'configuration/export.dart'; export 'configuration/import.dart'; -export 'configuration/encryption.dart'; -export 'configuration/filesystem.dart'; diff --git a/lib/core/configuration/import.dart b/lib/core/configuration/import.dart index 30346586..4d364be4 100644 --- a/lib/core/configuration/import.dart +++ b/lib/core/configuration/import.dart @@ -1,11 +1,13 @@ import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; import 'package:lunasea/core/database.dart'; class Import { Import._(); - static void _setLunaSea(Map data) { - LunaSeaDatabaseValue.ENABLED_PROFILE.put(data['profile']); + static void _setLunaSea(BuildContext context, Map data) { + LunaProfile.changeProfile(context, data['profile']); } static void _setProfiles(List data) { @@ -16,33 +18,28 @@ class Import { sonarrEnabled: profile['sonarrEnabled'] ?? false, sonarrHost: profile['sonarrHost'] ?? '', 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'] ?? '', nzbgetUser: profile['nzbgetUser'] ?? '', nzbgetPass: profile['nzbgetPass'] ?? '', - nzbgetStrictTLS: profile['nzbgetStrictTLS'] ?? true, nzbgetBasicAuth: profile['nzbgetBasicAuth'] ?? false, nzbgetHeaders: profile['nzbgetHeaders'] ?? {}, //Wake on LAN @@ -53,8 +50,12 @@ class Import { tautulliEnabled: profile['tautulliEnabled'] ?? false, tautulliHost: profile['tautulliHost'] ?? '', tautulliKey: profile['tautulliKey'] ?? '', - tautulliStrictTLS: profile['tautulliStrictTLS'] ?? true, tautulliHeaders: profile['tautulliHeaders'] ?? {}, + //Ombi + ombiEnabled: profile['ombiEnabled'] ?? false, + ombiHost: profile['ombiHost'] ?? '', + ombiKey: profile['ombiKey'] ?? '', + ombiHeaders: profile['ombiHeaders'] ?? {}, )); } } @@ -86,13 +87,13 @@ class Import { Database.clearProfilesBox(); } - static Future import(String data) async { + static Future import(BuildContext context, String data) async { Map _config = json.decode(data); if(_validate(_config)) { _clearBoxes(); _setProfiles(_config['profiles']); _setIndexers(_config['indexers']); - _setLunaSea(_config['lunasea']); + _setLunaSea(context, _config['lunasea']); return true; } return false; diff --git a/lib/core/constants.dart b/lib/core/constants.dart index e7a4e7fd..01f11571 100644 --- a/lib/core/constants.dart +++ b/lib/core/constants.dart @@ -13,10 +13,11 @@ import 'package:lunasea/modules.dart' show class Constants { Constants._(); - static const APPLICATION_NAME = "LunaSea"; - static const SENTRY_DSN = "https://511f76efcf714ecfb5ed6b26b5819bd6@o426090.ingest.sentry.io/5367513"; - //Services - static const Map MODULE_MAP = { + + static const APPLICATION_NAME = 'LunaSea'; + static const SENTRY_DSN = 'https://511f76efcf714ecfb5ed6b26b5819bd6@o426090.ingest.sentry.io/5367513'; + + static const Map MODULE_MAP = { LidarrConstants.MODULE_KEY: LidarrConstants.MODULE_MAP, RadarrConstants.MODULE_KEY: RadarrConstants.MODULE_MAP, SonarrConstants.MODULE_KEY: SonarrConstants.MODULE_MAP, @@ -27,27 +28,17 @@ class Constants { WakeOnLANConstants.MODULE_KEY: WakeOnLANConstants.MODULE_MAP, TautulliConstants.MODULE_KEY: TautulliConstants.MODULE_MAP, }; - //Colors - static const PRIMARY_COLOR = 0xFF32323E; - static const SECONDARY_COLOR = 0xFF282834; - static const ACCENT_COLOR = 0xFF4ECCA3; - static const SPLASH_COLOR = 0xFF2EA07B; - // - static const LIST_COLOR_ICONS = [ - Colors.blue, - Color(ACCENT_COLOR), - Colors.red, - Colors.orange, - Colors.purpleAccent, - Colors.blueGrey, - ]; - //Text + + static const EMPTY_MAP = {}; + static const EMPTY_LIST = []; + static const EMPTY_STRING = ''; + static const TEXT_EMDASH = '—'; static const TEXT_BULLET = '•'; static const TEXT_RARROW = '→'; static const TEXT_LARROW = '←'; static const TEXT_ELLIPSIS = '…'; - //UI + 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; @@ -59,19 +50,15 @@ class Constants { static const UI_FONT_SIZE_TITLE = 16.0; static const UI_FONT_SIZE_SUBTITLE = 13.0; static const UI_FONT_SIZE_GRAPH_LEGEND = 10.0; - //General - static const EMPTY_MAP = {}; - static const EMPTY_LIST = []; - static const EMPTY_STRING = ''; - //Error Values + static const CONFIGURATION_INVALID = '<>'; static const ENCRYPTION_FAILURE = '<>'; static const NO_SERVICES_ENABLED = '<>'; static const CHECK_LOGS_MESSAGE = 'Please check the logs for more details'; - //Extensions + static const BIT_SIZES = ['b', 'Kb', 'Mb', 'Gb', 'Tb', 'Pb', 'Eb']; static const BYTE_SIZES = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB']; - //URLs + static const URL_DOCUMENTATION = 'https://docs.lunasea.app'; static const URL_GITHUB = 'https://github.com/JagandeepBrar/LunaSea'; static const URL_REDDIT = 'https://www.reddit.com/r/LunaSeaApp'; @@ -83,7 +70,7 @@ class Constants { static const URL_SENTRY = 'https://sentry.io'; static const URL_TESTFLIGHT = 'https://testflight.apple.com/join/WWXaybra'; static const USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Safari/605.1.15'; - //Automation + static const Map historyReasonMessages = { 'Upgrade': 'Upgraded File', 'MissingFromDisk': 'Missing From Disk', diff --git a/lib/core/database/adapters/profile.dart b/lib/core/database/adapters/profile.dart index a8f5c25b..191cdfb6 100644 --- a/lib/core/database/adapters/profile.dart +++ b/lib/core/database/adapters/profile.dart @@ -5,8 +5,14 @@ import 'package:lunasea/modules.dart'; part 'profile.g.dart'; -//Next HiveField ID: 31 +//Next HiveField ID: 40 +/** + * Dead Fields + * 16, 17, 18, 19, 20, 34 + */ + +/// Hive database object containing all profile fields @HiveType(typeId: 0, adapterName: 'ProfileHiveObjectAdapter') class ProfileHiveObject extends HiveObject { factory ProfileHiveObject.empty() { @@ -15,33 +21,28 @@ class ProfileHiveObject extends HiveObject { lidarrEnabled: false, 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: '', nzbgetUser: '', nzbgetPass: '', - nzbgetStrictTLS: true, nzbgetBasicAuth: false, nzbgetHeaders: {}, //Wake on LAN @@ -52,8 +53,12 @@ class ProfileHiveObject extends HiveObject { tautulliEnabled: false, tautulliHost: '', tautulliKey: '', - tautulliStrictTLS: true, tautulliHeaders: {}, + //Ombi + ombiEnabled: false, + ombiHost: '', + ombiKey: '', + ombiHeaders: {}, ); } @@ -63,33 +68,28 @@ class ProfileHiveObject extends HiveObject { lidarrEnabled: obj.lidarrEnabled, 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, nzbgetUser: obj.nzbgetUser, nzbgetPass: obj.nzbgetPass, - nzbgetStrictTLS: obj.nzbgetStrictTLS, nzbgetBasicAuth: obj.nzbgetBasicAuth, nzbgetHeaders: obj.nzbgetHeaders, //Wake On LAN @@ -100,8 +100,12 @@ class ProfileHiveObject extends HiveObject { tautulliEnabled: obj.tautulliEnabled, tautulliHost: obj.tautulliHost, tautulliKey: obj.tautulliKey, - tautulliStrictTLS: obj.tautulliStrictTLS, tautulliHeaders: obj.tautulliHeaders, + //Ombi + ombiEnabled: obj.ombiEnabled, + ombiHost: obj.ombiHost, + ombiKey: obj.ombiKey, + ombiHeaders: obj.ombiHeaders, ); } @@ -110,33 +114,28 @@ class ProfileHiveObject extends HiveObject { @required this.lidarrEnabled, @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, @required this.nzbgetUser, @required this.nzbgetPass, - @required this.nzbgetStrictTLS, @required this.nzbgetBasicAuth, @required this.nzbgetHeaders, //Wake On LAN @@ -147,14 +146,16 @@ class ProfileHiveObject extends HiveObject { @required this.tautulliEnabled, @required this.tautulliHost, @required this.tautulliKey, - @required this.tautulliStrictTLS, @required this.tautulliHeaders, + //Ombi + @required this.ombiEnabled, + @required this.ombiHost, + @required this.ombiKey, + @required this.ombiHeaders, }); @override - String toString() { - return toMap().toString(); - } + String toString() => toMap().toString(); Map toMap() { return { @@ -163,33 +164,28 @@ class ProfileHiveObject extends HiveObject { "sonarrEnabled": sonarrEnabled, "sonarrHost": sonarrHost, "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, "nzbgetUser": nzbgetUser, "nzbgetPass": nzbgetPass, - "nzbgetStrictTLS": nzbgetStrictTLS, "nzbgetBasicAuth": nzbgetBasicAuth, "nzbgetHeaders": nzbgetHeaders, //Wake On LAN @@ -200,8 +196,12 @@ class ProfileHiveObject extends HiveObject { "tautulliEnabled": tautulliEnabled, "tautulliHost": tautulliHost, "tautulliKey": tautulliKey, - "tautulliStrictTLS": tautulliStrictTLS, "tautulliHeaders": tautulliHeaders, + //Ombi + "ombiEnabled": ombiEnabled, + "ombiHost": ombiHost, + "ombiKey": ombiKey, + "ombiHeaders": ombiHeaders, }; } @@ -212,8 +212,6 @@ class ProfileHiveObject extends HiveObject { String lidarrHost; @HiveField(2) String lidarrKey; - @HiveField(18) - bool lidarrStrictTLS; @HiveField(26) Map lidarrHeaders; @@ -221,7 +219,6 @@ class ProfileHiveObject extends HiveObject { 'enabled': lidarrEnabled ?? false, 'host': lidarrHost ?? '', 'key': lidarrKey ?? '', - 'strict_tls': lidarrStrictTLS ?? true, 'headers': lidarrHeaders ?? {}, }; @@ -232,8 +229,6 @@ class ProfileHiveObject extends HiveObject { String radarrHost; @HiveField(5) String radarrKey; - @HiveField(17) - bool radarrStrictTLS; @HiveField(27) Map radarrHeaders; @@ -241,7 +236,6 @@ class ProfileHiveObject extends HiveObject { 'enabled': radarrEnabled ?? false, 'host': radarrHost ?? '', 'key': radarrKey ?? '', - 'strict_tls': radarrStrictTLS ?? true, 'headers': radarrHeaders ?? {}, }; @@ -252,8 +246,6 @@ class ProfileHiveObject extends HiveObject { String sonarrHost; @HiveField(8) String sonarrKey; - @HiveField(16) - bool sonarrStrictTLS; @HiveField(21) bool sonarrVersion3; @HiveField(28) @@ -263,7 +255,6 @@ class ProfileHiveObject extends HiveObject { 'enabled': sonarrEnabled ?? false, 'host': sonarrHost ?? '', 'key': sonarrKey ?? '', - 'strict_tls': sonarrStrictTLS ?? true, 'v3': sonarrVersion3 ?? false, 'headers': sonarrHeaders ?? {}, }; @@ -275,8 +266,6 @@ class ProfileHiveObject extends HiveObject { String sabnzbdHost; @HiveField(11) String sabnzbdKey; - @HiveField(19) - bool sabnzbdStrictTLS; @HiveField(29) Map sabnzbdHeaders; @@ -284,7 +273,6 @@ class ProfileHiveObject extends HiveObject { 'enabled': sabnzbdEnabled ?? false, 'host': sabnzbdHost ?? '', 'key': sabnzbdKey ?? '', - 'strict_tls': sabnzbdStrictTLS ?? true, 'headers': sabnzbdHeaders ?? {}, }; @@ -297,8 +285,6 @@ class ProfileHiveObject extends HiveObject { String nzbgetUser; @HiveField(15) String nzbgetPass; - @HiveField(20) - bool nzbgetStrictTLS; @HiveField(22) bool nzbgetBasicAuth; @HiveField(30) @@ -309,7 +295,6 @@ class ProfileHiveObject extends HiveObject { 'host': nzbgetHost ?? '', 'user': nzbgetUser ?? '', 'pass': nzbgetPass ?? '', - 'strict_tls': nzbgetStrictTLS ?? true, 'basic_auth': nzbgetBasicAuth ?? false, 'headers': nzbgetHeaders ?? {}, }; @@ -335,8 +320,6 @@ class ProfileHiveObject extends HiveObject { String tautulliHost; @HiveField(33) String tautulliKey; - @HiveField(34) - bool tautulliStrictTLS; @HiveField(35) Map tautulliHeaders; @@ -344,10 +327,26 @@ class ProfileHiveObject extends HiveObject { 'enabled': tautulliEnabled ?? false, 'host': tautulliHost ?? '', 'key': tautulliKey ?? '', - 'strict_tls': tautulliStrictTLS ?? true, 'headers': tautulliHeaders ?? {}, }; + //Ombi + @HiveField(36) + bool ombiEnabled; + @HiveField(37) + String ombiHost; + @HiveField(38) + String ombiKey; + @HiveField(39) + Map ombiHeaders; + + Map getOmbi() => { + 'enabled': ombiEnabled ?? false, + 'host': ombiHost ?? '', + 'key': ombiKey ?? '', + 'headers': ombiHeaders ?? {}, + }; + List get enabledModules => [ ...enabledAutomationModules, ...enabledClientModules, @@ -367,17 +366,11 @@ class ProfileHiveObject extends HiveObject { List get enabledMonitoringModules => [ if(tautulliEnabled ?? false) TautulliConstants.MODULE_KEY, + if(ombiEnabled ?? false) OmbiConstants.MODULE_KEY, ]; bool get anyAutomationEnabled => enabledAutomationModules.isNotEmpty; bool get anyClientsEnabled => enabledClientModules.isNotEmpty; bool get anyMonitoringEnabled => enabledMonitoringModules.isNotEmpty; bool get anythingEnabled => anyAutomationEnabled || anyClientsEnabled || anyMonitoringEnabled; - - @override - Future save({ @required BuildContext context }) { - super.save(); - Providers.reset(context); - return null; - } } diff --git a/lib/core/database/adapters/profile.g.dart b/lib/core/database/adapters/profile.g.dart index 2449cc6c..8f5de72d 100644 --- a/lib/core/database/adapters/profile.g.dart +++ b/lib/core/database/adapters/profile.g.dart @@ -20,29 +20,24 @@ class ProfileHiveObjectAdapter extends TypeAdapter { lidarrEnabled: fields[0] as bool, lidarrHost: fields[1] as String, lidarrKey: fields[2] as String, - lidarrStrictTLS: fields[18] as bool, lidarrHeaders: (fields[26] as Map)?.cast(), radarrEnabled: fields[3] as bool, radarrHost: fields[4] as String, radarrKey: fields[5] as String, - radarrStrictTLS: fields[17] as bool, radarrHeaders: (fields[27] as Map)?.cast(), sonarrEnabled: fields[6] as bool, sonarrHost: fields[7] as String, sonarrKey: fields[8] as String, - sonarrStrictTLS: fields[16] as bool, sonarrVersion3: fields[21] as bool, sonarrHeaders: (fields[28] as Map)?.cast(), sabnzbdEnabled: fields[9] as bool, sabnzbdHost: fields[10] as String, sabnzbdKey: fields[11] as String, - sabnzbdStrictTLS: fields[19] as bool, sabnzbdHeaders: (fields[29] as Map)?.cast(), nzbgetEnabled: fields[12] as bool, nzbgetHost: fields[13] as String, nzbgetUser: fields[14] as String, nzbgetPass: fields[15] as String, - nzbgetStrictTLS: fields[20] as bool, nzbgetBasicAuth: fields[22] as bool, nzbgetHeaders: (fields[30] as Map)?.cast(), wakeOnLANEnabled: fields[23] as bool, @@ -51,23 +46,24 @@ class ProfileHiveObjectAdapter extends TypeAdapter { tautulliEnabled: fields[31] as bool, tautulliHost: fields[32] as String, tautulliKey: fields[33] as String, - tautulliStrictTLS: fields[34] as bool, tautulliHeaders: (fields[35] as Map)?.cast(), + ombiEnabled: fields[36] as bool, + ombiHost: fields[37] as String, + ombiKey: fields[38] as String, + ombiHeaders: (fields[39] as Map)?.cast(), ); } @override void write(BinaryWriter writer, ProfileHiveObject obj) { writer - ..writeByte(36) + ..writeByte(34) ..writeByte(0) ..write(obj.lidarrEnabled) ..writeByte(1) ..write(obj.lidarrHost) ..writeByte(2) ..write(obj.lidarrKey) - ..writeByte(18) - ..write(obj.lidarrStrictTLS) ..writeByte(26) ..write(obj.lidarrHeaders) ..writeByte(3) @@ -76,8 +72,6 @@ class ProfileHiveObjectAdapter extends TypeAdapter { ..write(obj.radarrHost) ..writeByte(5) ..write(obj.radarrKey) - ..writeByte(17) - ..write(obj.radarrStrictTLS) ..writeByte(27) ..write(obj.radarrHeaders) ..writeByte(6) @@ -86,8 +80,6 @@ class ProfileHiveObjectAdapter extends TypeAdapter { ..write(obj.sonarrHost) ..writeByte(8) ..write(obj.sonarrKey) - ..writeByte(16) - ..write(obj.sonarrStrictTLS) ..writeByte(21) ..write(obj.sonarrVersion3) ..writeByte(28) @@ -98,8 +90,6 @@ class ProfileHiveObjectAdapter extends TypeAdapter { ..write(obj.sabnzbdHost) ..writeByte(11) ..write(obj.sabnzbdKey) - ..writeByte(19) - ..write(obj.sabnzbdStrictTLS) ..writeByte(29) ..write(obj.sabnzbdHeaders) ..writeByte(12) @@ -110,8 +100,6 @@ class ProfileHiveObjectAdapter extends TypeAdapter { ..write(obj.nzbgetUser) ..writeByte(15) ..write(obj.nzbgetPass) - ..writeByte(20) - ..write(obj.nzbgetStrictTLS) ..writeByte(22) ..write(obj.nzbgetBasicAuth) ..writeByte(30) @@ -128,10 +116,16 @@ class ProfileHiveObjectAdapter extends TypeAdapter { ..write(obj.tautulliHost) ..writeByte(33) ..write(obj.tautulliKey) - ..writeByte(34) - ..write(obj.tautulliStrictTLS) ..writeByte(35) - ..write(obj.tautulliHeaders); + ..write(obj.tautulliHeaders) + ..writeByte(36) + ..write(obj.ombiEnabled) + ..writeByte(37) + ..write(obj.ombiHost) + ..writeByte(38) + ..write(obj.ombiKey) + ..writeByte(39) + ..write(obj.ombiHeaders); } @override diff --git a/lib/core/database/database.dart b/lib/core/database/database.dart index 98bc985a..78b2be89 100644 --- a/lib/core/database/database.dart +++ b/lib/core/database/database.dart @@ -15,6 +15,10 @@ import 'package:lunasea/modules.dart' show export 'package:hive/hive.dart'; export 'package:hive_flutter/hive_flutter.dart'; +/// Next HiveType: 16 +/// +/// Dead Fields: + class Database { Database._(); @@ -59,7 +63,6 @@ class Database { //Set default profile & enabled profile profilesBox.put('default', ProfileHiveObject.empty()); lunaSeaBox.put(LunaSeaDatabaseValue.ENABLED_PROFILE.key, 'default'); - } //Get boxes diff --git a/lib/core/dialogs.dart b/lib/core/dialogs.dart index 45d8eb07..82ed87b9 100644 --- a/lib/core/dialogs.dart +++ b/lib/core/dialogs.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:lunasea/core.dart'; -class GlobalDialogs { - GlobalDialogs._(); +class LunaDialogs { + LunaDialogs._(); static Future> editText(BuildContext context, String title, { String prefill = '' }) async { bool _flag = false; @@ -95,7 +95,7 @@ class GlobalDialogs { buttons: [ LSDialog.button( text: 'Delete', - textColor: LSColors.red, + textColor: LunaColours.red, onPressed: () => _setValues(true), ), ], diff --git a/lib/core/configuration/encryption.dart b/lib/core/encryption.dart similarity index 75% rename from lib/core/configuration/encryption.dart rename to lib/core/encryption.dart index c6413b4b..2eb1e560 100644 --- a/lib/core/configuration/encryption.dart +++ b/lib/core/encryption.dart @@ -1,8 +1,8 @@ import 'package:encrypt/encrypt.dart'; import 'package:lunasea/core.dart'; -class Encryption { - Encryption._(); +class LunaEncryption { + LunaEncryption._(); static String encrypt(String encryptionKey, String data) { try { @@ -13,7 +13,7 @@ class Encryption { final _encrypted = _encrypter.encrypt(data, iv: iv).base64; return _encrypted; } catch (e) { - Logger.error('package:lunasea/core/configuration/encryption.dart', 'encrypt', 'Encryption error', e, StackTrace.current); + LunaLogger.error('package:lunasea/core/configuration/encryption.dart', 'encrypt', 'Encryption error', e, StackTrace.current); } return Constants.ENCRYPTION_FAILURE; } @@ -26,7 +26,7 @@ class Encryption { final _encrypter = Encrypter(AES(key)); return _encrypter.decrypt64(data, iv: iv); } catch (e) { - Logger.error('package:lunasea/core/configuration/encryption.dart', 'decrypt', 'Decryption error', e, StackTrace.current); + LunaLogger.error('package:lunasea/core/configuration/encryption.dart', 'decrypt', 'Decryption error', e, StackTrace.current); } return Constants.ENCRYPTION_FAILURE; } diff --git a/lib/core/extensions/flog/log_level.dart b/lib/core/extensions/flog/log_level.dart index 4d730bcd..d013891b 100644 --- a/lib/core/extensions/flog/log_level.dart +++ b/lib/core/extensions/flog/log_level.dart @@ -11,7 +11,7 @@ extension FLogLogLevelExtension on FLog.LogLevel { switch(this.toString()) { case 'LogLevel.WARNING': return Colors.orange; case 'LogLevel.ERROR': return Colors.red; - case 'LogLevel.FATAL': return LSColors.accent; + case 'LogLevel.FATAL': return LunaColours.accent; default: return Colors.blueGrey; } } diff --git a/lib/core/extensions/in_app_purchases.dart b/lib/core/extensions/in_app_purchases.dart index 2c90f8ce..47cb7cee 100644 --- a/lib/core/extensions/in_app_purchases.dart +++ b/lib/core/extensions/in_app_purchases.dart @@ -6,10 +6,10 @@ extension ProductDetailsExtension on ProductDetails { //ignore: non_constant_identifier_names IconData get ls_Icon { switch(this.id) { - case InAppPurchases.IAP_ID_DONATION_01: return Icons.local_drink; - case InAppPurchases.IAP_ID_DONATION_03: return Icons.local_cafe; - case InAppPurchases.IAP_ID_DONATION_05: return Icons.local_bar; - case InAppPurchases.IAP_ID_DONATION_10: return Icons.fastfood; + case LunaInAppPurchases.IAP_ID_DONATION_01: return Icons.local_drink; + case LunaInAppPurchases.IAP_ID_DONATION_03: return Icons.local_cafe; + case LunaInAppPurchases.IAP_ID_DONATION_05: return Icons.local_bar; + case LunaInAppPurchases.IAP_ID_DONATION_10: return Icons.fastfood; default: return Icons.attach_money; } } @@ -17,10 +17,10 @@ extension ProductDetailsExtension on ProductDetails { //ignore: non_constant_identifier_names String get ls_Name { switch(this.id) { - case InAppPurchases.IAP_ID_DONATION_01: return 'Buy Me A Soda'; - case InAppPurchases.IAP_ID_DONATION_03: return 'Buy Me A Coffee'; - case InAppPurchases.IAP_ID_DONATION_05: return 'Buy Me A Beer'; - case InAppPurchases.IAP_ID_DONATION_10: return 'Buy Me A Burger'; + case LunaInAppPurchases.IAP_ID_DONATION_01: return 'Buy Me A Soda'; + case LunaInAppPurchases.IAP_ID_DONATION_03: return 'Buy Me A Coffee'; + case LunaInAppPurchases.IAP_ID_DONATION_05: return 'Buy Me A Beer'; + case LunaInAppPurchases.IAP_ID_DONATION_10: return 'Buy Me A Burger'; default: return 'Unknown In-App Purchase'; } } diff --git a/lib/core/configuration/filesystem.dart b/lib/core/filesystem.dart similarity index 97% rename from lib/core/configuration/filesystem.dart rename to lib/core/filesystem.dart index f018251f..ff1c8285 100644 --- a/lib/core/configuration/filesystem.dart +++ b/lib/core/filesystem.dart @@ -2,8 +2,8 @@ import 'dart:io'; import 'package:intl/intl.dart'; import 'package:path_provider/path_provider.dart'; -class Filesystem { - Filesystem._(); +class LunaFileSystem { + LunaFileSystem._(); static String get configFileName { String _now = DateFormat('y-MM-dd kk-mm-ss').format(DateTime.now()); diff --git a/lib/core/in_app_purchases.dart b/lib/core/in_app_purchases.dart index 811230f1..4f78dc4f 100644 --- a/lib/core/in_app_purchases.dart +++ b/lib/core/in_app_purchases.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:in_app_purchase/in_app_purchase.dart'; -class InAppPurchases { +class LunaInAppPurchases { static const IAP_ID_DONATION_01 = 'donation_01'; static const IAP_ID_DONATION_03 = 'donation_03'; static const IAP_ID_DONATION_05 = 'donation_05'; @@ -21,7 +21,7 @@ class InAppPurchases { static Future initialize() async { InAppPurchaseConnection.enablePendingPurchases(); - purchaseStream = connection.purchaseUpdatedStream.listen((data) => InAppPurchases._purchasedCallback(data)); + purchaseStream = connection.purchaseUpdatedStream.listen((data) => LunaInAppPurchases._purchasedCallback(data)); available = await connection.isAvailable(); ProductDetailsResponse _resp = await connection.queryProductDetails(Set.from(IAP_IDS)); products = _resp.productDetails; diff --git a/lib/core/logger.dart b/lib/core/logger.dart index 69b69df8..f86c6a41 100644 --- a/lib/core/logger.dart +++ b/lib/core/logger.dart @@ -7,8 +7,8 @@ import 'package:stack_trace/stack_trace.dart'; import 'package:f_logs/f_logs.dart' show FLog, DataLogType, FormatType, LogsConfig; export 'package:dio/dio.dart' show DioError; -class Logger { - Logger._(); +class LunaLogger { + LunaLogger._(); static final SentryClient _sentry = SentryClient(dsn: Constants.SENTRY_DSN); static void initialize() { @@ -66,9 +66,10 @@ class Logger { DataLogType type = DataLogType.DEFAULT, bool uploadToSentry = true, }) { + Trace _trace = Trace.from(trace); FLog.fatal( - className: Trace.from(trace).frames[1].uri.toString() ?? 'Unknown', - methodName: Trace.from(trace).frames[1].member.toString() ?? 'Unknown', + className: _trace.frames.length >= 1 ? _trace.frames[1].uri.toString() ?? 'Unknown' : 'Unknown', + methodName: _trace.frames.length >= 1 ? _trace.frames[1].member.toString() ?? 'Unknown' : 'Unknown', text: error.toString(), exception: error, stacktrace: trace, diff --git a/lib/core/luna_ui.dart b/lib/core/luna_ui.dart new file mode 100644 index 00000000..353f83dd --- /dev/null +++ b/lib/core/luna_ui.dart @@ -0,0 +1,8 @@ +export 'luna_ui/appbar.dart'; +export 'luna_ui/table.dart'; + +class LunaUI { + static const double FONT_SIZE_APPBAR = 18.0; + + LunaUI._(); +} diff --git a/lib/core/luna_ui/appbar.dart b/lib/core/luna_ui/appbar.dart new file mode 100644 index 00000000..98f3c3ea --- /dev/null +++ b/lib/core/luna_ui/appbar.dart @@ -0,0 +1 @@ +export 'appbar/appbar.dart'; diff --git a/lib/core/luna_ui/appbar/appbar.dart b/lib/core/luna_ui/appbar/appbar.dart new file mode 100644 index 00000000..386441ec --- /dev/null +++ b/lib/core/luna_ui/appbar/appbar.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; + +class LunaAppBar extends AppBar { + LunaAppBar({ + @required BuildContext context, + @required String title, + @required String popUntil, + List actions, + PreferredSizeWidget bottom, + bool hideLeading = false, + }) : super( + title: Text( + title ?? '', + overflow: TextOverflow.fade, + style: TextStyle( + fontSize: LunaUI.FONT_SIZE_APPBAR, + ), + ), + leading: hideLeading ? null : InkWell( + child: Icon(Icons.arrow_back_ios), + onTap: () async => Navigator.of(context).pop(), + onLongPress: () async => popUntil == null + ? Navigator.of(context).pop() + : Navigator.of(context).popUntil(ModalRoute.withName(popUntil)), + borderRadius: BorderRadius.circular(28.0), + ), + centerTitle: false, + elevation: 0, + actions: actions, + bottom: bottom, + ); + + LunaAppBar.empty({ + @required Widget child, + @required double height, + }) : super( + automaticallyImplyLeading: false, + toolbarHeight: height, + leadingWidth: 0.0, + elevation: 0.0, + titleSpacing: 0.0, + title: child, + ); +} + diff --git a/lib/core/luna_ui/table.dart b/lib/core/luna_ui/table.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/core/module_map.dart b/lib/core/module_map.dart index f51888a8..97e23376 100644 --- a/lib/core/module_map.dart +++ b/lib/core/module_map.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -class ModuleMap { +class LunaModuleMap { final String name; final String description; final String settingsDescription; @@ -8,7 +8,7 @@ class ModuleMap { final IconData icon; final Color color; - const ModuleMap({ + const LunaModuleMap({ @required this.name, @required this.description, @required this.settingsDescription, diff --git a/lib/core/networking.dart b/lib/core/networking.dart new file mode 100644 index 00000000..2cbbe827 --- /dev/null +++ b/lib/core/networking.dart @@ -0,0 +1,12 @@ +import 'dart:io'; + +class LunaNetworking extends HttpOverrides { + static void initialize() => HttpOverrides.global = LunaNetworking(); + + @override + HttpClient createHttpClient(SecurityContext context) { + final HttpClient client = super.createHttpClient(context); + client.badCertificateCallback = (X509Certificate cert, String host, int port) => true; + return client; + } +} diff --git a/lib/core/profile.dart b/lib/core/profile.dart new file mode 100644 index 00000000..88504cf5 --- /dev/null +++ b/lib/core/profile.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; + +class LunaProfile { + LunaProfile._(); + + static Future changeProfile(BuildContext context, String profile) async { + if(LunaSeaDatabaseValue.ENABLED_PROFILE.data != profile) { + if(Database.profilesBox.containsKey(profile)) { + LunaSeaDatabaseValue.ENABLED_PROFILE.put(profile); + LunaProvider.reset(context); + LSSnackBar(context: context, title: 'Changed Profile', message: profile); + return true; + } else { + LunaLogger.warning('LunaProfile', 'changeProfile', 'Attempted to change profile to unknown profile: $profile'); + } + } + return false; + } +} diff --git a/lib/core/providers.dart b/lib/core/providers.dart index 6e3a54cd..21360b6e 100644 --- a/lib/core/providers.dart +++ b/lib/core/providers.dart @@ -6,42 +6,51 @@ import 'package:provider/provider.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules.dart' show HomeState, - LidarrModel, - NZBGetModel, - RadarrState, - SABnzbdModel, - SearchModel, + SearchState, SettingsState, - SonarrModel, + LidarrState, + RadarrState, + SonarrState, + NZBGetState, + SABnzbdState, + OmbiState, TautulliState; -class Providers { - Providers._(); +class LunaProvider { + LunaProvider._(); static void reset(BuildContext context) { - Provider.of(context, listen: false).reset(); // General Provider.of(context, listen: false).reset(); + Provider.of(context, listen: false).reset(); Provider.of(context, listen: false).reset(); + // Automation + Provider.of(context, listen: false).reset(); + Provider.of(context, listen: false).reset(); + Provider.of(context, listen: false).reset(); + // Clients + Provider.of(context, listen: false).reset(); + Provider.of(context, listen: false).reset(); // Monitoring + Provider.of(context, listen: false).reset(); Provider.of(context, listen: false).reset(); } static MultiProvider providers({ @required Widget child }) => MultiProvider( providers: [ - ChangeNotifierProvider(create: (_) => LunaSeaState()), // General ChangeNotifierProvider(create: (_) => HomeState()), - ChangeNotifierProvider(create: (_) => SearchModel()), + ChangeNotifierProvider(create: (_) => SearchState()), ChangeNotifierProvider(create: (_) => SettingsState()), // Automation - ChangeNotifierProvider(create: (_) => SonarrModel()), - ChangeNotifierProvider(create: (_) => LidarrModel()), + ChangeNotifierProvider(create: (_) => SonarrState()), + ChangeNotifierProvider(create: (_) => LidarrState()), ChangeNotifierProvider(create: (_) => RadarrState()), // Clients - ChangeNotifierProvider(create: (_) => NZBGetModel()), - ChangeNotifierProvider(create: (_) => SABnzbdModel()), + ChangeNotifierProvider(create: (_) => NZBGetState()), + ChangeNotifierProvider(create: (_) => SABnzbdState()), // Monitoring + ChangeNotifierProvider(create: (_) => OmbiState()), ChangeNotifierProvider(create: (_) => TautulliState()), ], child: child, diff --git a/lib/core/homescreen_actions.dart b/lib/core/quick_actions.dart similarity index 60% rename from lib/core/homescreen_actions.dart rename to lib/core/quick_actions.dart index 5a1a4aa0..36c2e9c3 100644 --- a/lib/core/homescreen_actions.dart +++ b/lib/core/quick_actions.dart @@ -5,10 +5,10 @@ import 'package:lunasea/modules.dart'; import 'package:lunasea/main.dart'; export 'package:quick_actions/quick_actions.dart' show ShortcutItem; -class HomescreenActions { +class LunaQuickActions { static final QuickActions _quickActions = QuickActions(); - HomescreenActions._(); + LunaQuickActions._(); static void initialize(BuildContext context) { _quickActions.initialize((action) => _handler(context, action)); @@ -37,15 +37,17 @@ class HomescreenActions { case NZBGetConstants.MODULE_KEY: _pushNZBGet(); break; case SABnzbdConstants.MODULE_KEY: _pushSABnzbd(); break; case TautulliConstants.MODULE_KEY: _pushTautulli(); break; + case OmbiConstants.MODULE_KEY: _pushOmbi(); break; } } } - static void _pushSearch() => BIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(Search.ROUTE_NAME, (Route route) => false); - static void _pushLidarr() => BIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(Lidarr.ROUTE_NAME, (Route route) => false); - static void _pushRadarr() => BIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(Radarr.ROUTE_NAME, (Route route) => false); - static void _pushSonarr() => BIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(Sonarr.ROUTE_NAME, (Route route) => false); - static void _pushNZBGet() => BIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(NZBGet.ROUTE_NAME, (Route route) => false); - static void _pushSABnzbd() => BIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(SABnzbd.ROUTE_NAME, (Route route) => false); - static void _pushTautulli() => BIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(TautulliModule.ROUTE_NAME, (Route route) => false); + static void _pushSearch() => LunaBIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(Search.ROUTE_NAME, (Route route) => false); + static void _pushLidarr() => LunaBIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(Lidarr.ROUTE_NAME, (Route route) => false); + static void _pushRadarr() => LunaBIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(Radarr.ROUTE_NAME, (Route route) => false); + static void _pushSonarr() => LunaBIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(SonarrHomeRouter.route(), (Route route) => false); + static void _pushNZBGet() => LunaBIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(NZBGet.ROUTE_NAME, (Route route) => false); + static void _pushSABnzbd() => LunaBIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(SABnzbd.ROUTE_NAME, (Route route) => false); + static void _pushOmbi() => LunaBIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(OmbiHomeRouter.route(), (Route route) => false); + static void _pushTautulli() => LunaBIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(TautulliHomeRouter.route(), (Route route) => false); } diff --git a/lib/core/router.dart b/lib/core/router.dart index 5716a425..cc36e772 100644 --- a/lib/core/router.dart +++ b/lib/core/router.dart @@ -1,37 +1,37 @@ import 'package:flutter/material.dart' hide Router; import 'package:fluro_fork/fluro_fork.dart'; import 'package:lunasea/modules/home/routes.dart'; -import 'package:lunasea/modules/settings.dart' show SettingsModule, SettingsRouter; import 'package:lunasea/modules/search/routes.dart'; import 'package:lunasea/modules/lidarr/routes.dart'; import 'package:lunasea/modules/radarr/routes.dart'; -import 'package:lunasea/modules/sonarr/routes.dart'; import 'package:lunasea/modules/nzbget/routes.dart'; import 'package:lunasea/modules/sabnzbd/routes.dart'; -import 'package:lunasea/modules/tautulli.dart' show TautulliModule, TautulliRouter; +import 'package:lunasea/modules/settings.dart' show SettingsRouter; +import 'package:lunasea/modules/sonarr.dart' show SonarrRouter; +import 'package:lunasea/modules/ombi.dart' show OmbiRouter; +import 'package:lunasea/modules/tautulli.dart' show TautulliRouter; class LunaRouter { + static Router router = Router(); + LunaRouter._(); static void intialize() { - // General - SettingsRouter.initialize(); - // Monitoring - TautulliRouter.initialize(); + SettingsRouter.initialize(router); + SonarrRouter.initialize(router); + OmbiRouter.initialize(router); + TautulliRouter.initialize(router); } static TransitionType get transitionType => TransitionType.native; static Map get routes => { ..._home, - SettingsModule.ROUTE_NAME: (context) => SettingsModule(), ..._search, ..._lidarr, ..._radarr, - ..._sonarr, ..._sabnzbd, ..._nzbget, - TautulliModule.ROUTE_NAME: (context) => TautulliModule(), }; static Map get _home => { @@ -88,25 +88,6 @@ class LunaRouter { RadarrSearchResults.ROUTE_NAME: (context) => RadarrSearchResults(), }; - static Map get _sonarr => { - // /sonarr - Sonarr.ROUTE_NAME: (context) => Sonarr(), - // /sonarr/* - SonarrCatalogue.ROUTE_NAME: (context) => SonarrCatalogue(refreshIndicatorKey: null, refreshAllPages: null), - SonarrMissing.ROUTE_NAME: (context) => SonarrMissing(refreshIndicatorKey: null, refreshAllPages: null), - SonarrUpcoming.ROUTE_NAME: (context) => SonarrUpcoming(refreshIndicatorKey: null, refreshAllPages: null), - SonarrHistory.ROUTE_NAME: (context) => SonarrHistory(refreshIndicatorKey: null, refreshAllPages: null), - // /sonarr/add/* - SonarrAddSearch.ROUTE_NAME: (context) => SonarrAddSearch(), - SonarrAddDetails.ROUTE_NAME: (context) => SonarrAddDetails(), - // /sonarr/details/* - SonarrDetailsSeries.ROUTE_NAME: (context) => SonarrDetailsSeries(), - SonarrDetailsSeason.ROUTE_NAME: (context) => SonarrDetailsSeason(), - // /sonarr/*/* - SonarrEditSeries.ROUTE_NAME: (context) => SonarrEditSeries(), - SonarrSearchResults.ROUTE_NAME: (context) => SonarrSearchResults(), - }; - static Map get _nzbget => { // /nzbget NZBGet.ROUTE_NAME: (context) => NZBGet(), diff --git a/lib/core/state.dart b/lib/core/state.dart deleted file mode 100644 index 772220da..00000000 --- a/lib/core/state.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:flutter/foundation.dart'; - -class LunaSeaState extends ChangeNotifier { - LunaSeaState() { - reset(initialize: true); - } - - /// Reset the state of LunaSea back to the default - void reset({ bool initialize = false }) { - if(initialize) {} - notifyListeners(); - } -} diff --git a/lib/core/theme.dart b/lib/core/theme.dart index a201fc87..2b5fd2b6 100644 --- a/lib/core/theme.dart +++ b/lib/core/theme.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:lunasea/core.dart'; -class Themes { - Themes._(); +class LunaTheme { + LunaTheme._(); - static ThemeData getDarkTheme() => LunaSeaDatabaseValue.THEME_AMOLED.data + static ThemeData get darkTheme => LunaSeaDatabaseValue.THEME_AMOLED.data ? _pureBlackTheme() : _midnightTheme(); @@ -15,16 +15,16 @@ class Themes { ); return ThemeData( brightness: Brightness.dark, - canvasColor: LSColors.primary, - primaryColor: LSColors.secondary, - accentColor: LSColors.accent, - highlightColor: LSColors.secondary, - cardColor: LSColors.secondary, - splashColor: LSColors.splash, - dialogBackgroundColor: LSColors.secondary, - dividerColor: LSColors.accent.withAlpha(0), + canvasColor: LunaColours.primary, + primaryColor: LunaColours.secondary, + accentColor: LunaColours.accent, + highlightColor: LunaColours.secondary, + cardColor: LunaColours.secondary, + splashColor: LunaColours.splash, + dialogBackgroundColor: LunaColours.secondary, + dividerColor: LunaColours.accent.withAlpha(0), dividerTheme: DividerThemeData( - color: LSColors.accent, + color: LunaColours.accent, indent: 100.0, endIndent: 100.0, ), @@ -59,14 +59,14 @@ class Themes { brightness: Brightness.dark, canvasColor: Colors.black, primaryColor: Colors.black, - accentColor: LSColors.accent, - highlightColor: LSColors.secondary, + accentColor: LunaColours.accent, + highlightColor: LunaColours.secondary, cardColor: Colors.black, - splashColor: LSColors.splash, + splashColor: LunaColours.splash, dialogBackgroundColor: Colors.black, - dividerColor: LSColors.accent.withAlpha(0), + dividerColor: LunaColours.accent.withAlpha(0), dividerTheme: DividerThemeData( - color: LSColors.accent, + color: LunaColours.accent, indent: 72.0, endIndent: 72.0, ), diff --git a/lib/core/types.dart b/lib/core/types.dart index af4f984a..7966ed0f 100644 --- a/lib/core/types.dart +++ b/lib/core/types.dart @@ -1,2 +1,4 @@ export 'types/api.dart'; +export 'types/loading.dart'; export 'types/sorter.dart'; +export 'types/state_global.dart'; diff --git a/lib/core/types/loading.dart b/lib/core/types/loading.dart new file mode 100644 index 00000000..fd282e06 --- /dev/null +++ b/lib/core/types/loading.dart @@ -0,0 +1,5 @@ +enum LunaLoadingState { + ACTIVE, + INACTIVE, + ERROR, +} diff --git a/lib/core/types/sorter.dart b/lib/core/types/sorter.dart index 61c8d227..0dfa9cf3 100644 --- a/lib/core/types/sorter.dart +++ b/lib/core/types/sorter.dart @@ -1,3 +1,3 @@ -abstract class Sorter { +abstract class LunaSorter { dynamic byType(List data, T type, bool ascending); } diff --git a/lib/core/types/state_global.dart b/lib/core/types/state_global.dart new file mode 100644 index 00000000..c5f72d52 --- /dev/null +++ b/lib/core/types/state_global.dart @@ -0,0 +1,9 @@ +import 'package:flutter/material.dart'; + +abstract class LunaGlobalState extends ChangeNotifier { + /// Reset the state back to the default + void reset(); + + /// Notify listeners of an update + void notify() => notifyListeners(); +} diff --git a/lib/core/ui/appbar.dart b/lib/core/ui/appbar.dart index 69d454b5..1a67a3f2 100644 --- a/lib/core/ui/appbar.dart +++ b/lib/core/ui/appbar.dart @@ -1,2 +1 @@ -export 'appbar/appbar.dart'; export 'appbar/appbar_dropdown.dart'; diff --git a/lib/core/ui/appbar/appbar.dart b/lib/core/ui/appbar/appbar.dart deleted file mode 100644 index 1a275826..00000000 --- a/lib/core/ui/appbar/appbar.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; - -// ignore: non_constant_identifier_names -Widget LSAppBar({ - @required String title, - List actions -}) => AppBar( - title: Text( - title, - overflow: TextOverflow.fade, - style: TextStyle( - fontSize: Constants.UI_FONT_SIZE_HEADER, - ), - ), - centerTitle: false, - elevation: 0, - actions: actions, -); diff --git a/lib/core/ui/appbar/appbar_dropdown.dart b/lib/core/ui/appbar/appbar_dropdown.dart index e890feab..dc6a1b23 100644 --- a/lib/core/ui/appbar/appbar_dropdown.dart +++ b/lib/core/ui/appbar/appbar_dropdown.dart @@ -8,49 +8,40 @@ Widget LSAppBarDropdown({ @required List profiles, List actions }) => profiles != null && profiles.length < 2 - ? LSAppBar(title: title, actions: actions) + ? LunaAppBar(context: context, title: title, actions: actions, popUntil: null, hideLeading: true) : AppBar( - title: PopupMenuButton( - shape: LunaSeaDatabaseValue.THEME_AMOLED.data && LunaSeaDatabaseValue.THEME_AMOLED_BORDER.data - ? LSRoundedShapeWithBorder() - : LSRoundedShape(), - child: Wrap( - direction: Axis.horizontal, - children: [ - Text( - title, - style: TextStyle( - fontSize: Constants.UI_FONT_SIZE_HEADER, + title: PopupMenuButton( + shape: LunaSeaDatabaseValue.THEME_AMOLED.data && LunaSeaDatabaseValue.THEME_AMOLED_BORDER.data + ? LSRoundedShapeWithBorder() + : LSRoundedShape(), + child: Wrap( + direction: Axis.horizontal, + children: [ + Text( + title, + style: TextStyle( + fontSize: Constants.UI_FONT_SIZE_HEADER, + ), ), - ), - LSIcon( - icon: Icons.arrow_drop_down, - ), - ], + LSIcon( + icon: Icons.arrow_drop_down, + ), + ], + ), + onSelected: (result) => LunaProfile.changeProfile(context, result), + itemBuilder: (context) { + return >[for(String profile in profiles) PopupMenuItem( + value: profile, + child: Text( + profile, + style: TextStyle( + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + ), + ), + )]; + }, ), - onSelected: (result) { - LunaSeaDatabaseValue.ENABLED_PROFILE.put(result); - Providers.reset(context); - LSSnackBar( - context: context, - title: 'Changed Profile', - message: 'Using profile "$result"', - type: SNACKBAR_TYPE.info, - ); - }, - itemBuilder: (context) { - return >[for(String profile in profiles) PopupMenuItem( - value: profile, - child: Text( - profile, - style: TextStyle( - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - ), - ), - )]; - }, - ), - centerTitle: false, - elevation: 0, - actions: actions, -); + centerTitle: false, + elevation: 0, + actions: actions, + ); diff --git a/lib/core/ui/button/button.dart b/lib/core/ui/button/button.dart index b8f6d8c6..0568e200 100644 --- a/lib/core/ui/button/button.dart +++ b/lib/core/ui/button/button.dart @@ -11,7 +11,7 @@ class LSButton extends StatelessWidget { LSButton({ @required this.text, @required this.onTap, - this.backgroundColor = const Color(Constants.ACCENT_COLOR), + this.backgroundColor = const Color(LunaColours.ACCENT_COLOR), this.textColor = Colors.white, this.reducedMargin = false, }); diff --git a/lib/core/ui/button/button_slim.dart b/lib/core/ui/button/button_slim.dart index 21ceab06..16bc2fd5 100644 --- a/lib/core/ui/button/button_slim.dart +++ b/lib/core/ui/button/button_slim.dart @@ -11,7 +11,7 @@ class LSButtonSlim extends StatelessWidget { LSButtonSlim({ @required this.text, @required this.onTap, - this.backgroundColor = const Color(Constants.ACCENT_COLOR), + this.backgroundColor = const Color(LunaColours.ACCENT_COLOR), this.textColor = Colors.white, this.margin = const EdgeInsets.symmetric(horizontal: 6.0, vertical: 6.0), }); diff --git a/lib/core/ui/card/card_background.dart b/lib/core/ui/card/card_background.dart index d2f11333..141d82c7 100644 --- a/lib/core/ui/card/card_background.dart +++ b/lib/core/ui/card/card_background.dart @@ -2,13 +2,13 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; // ignore: non_constant_identifier_names -Decoration LSCardBackground({ @required String uri, @required Map headers, bool darken = false }) => BoxDecoration( +Decoration LSCardBackground({ @required String uri, @required Map headers }) => BoxDecoration( image: DecorationImage( image: NetworkImage( uri, headers: Map.from(headers), ), - colorFilter: ColorFilter.mode(LSColors.secondary.withOpacity(darken ? 0.10 : 0.20), BlendMode.dstATop), + colorFilter: ColorFilter.mode(LunaColours.secondary.withOpacity(0.10), BlendMode.dstATop), fit: BoxFit.cover, ), borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), diff --git a/lib/core/ui/colors.dart b/lib/core/ui/colors.dart index 5f4645a1..4083cbe2 100644 --- a/lib/core/ui/colors.dart +++ b/lib/core/ui/colors.dart @@ -1,13 +1,26 @@ import 'package:flutter/material.dart'; -import 'package:lunasea/core/constants.dart'; -class LSColors { - LSColors._(); +class LunaColours { + LunaColours._(); - static Color get accent => const Color(Constants.ACCENT_COLOR); - static Color get primary => const Color(Constants.PRIMARY_COLOR); - static Color get secondary => const Color(Constants.SECONDARY_COLOR); - static Color get splash => const Color(Constants.SPLASH_COLOR); + static const PRIMARY_COLOR = 0xFF32323E; + static const SECONDARY_COLOR = 0xFF282834; + static const ACCENT_COLOR = 0xFF4ECCA3; + static const SPLASH_COLOR = 0xFF2EA07B; + + static const LIST_COLOR_ICONS = [ + Colors.blue, + Color(ACCENT_COLOR), + Colors.red, + Colors.orange, + Colors.purpleAccent, + Colors.blueGrey, + ]; + + static Color get accent => const Color(ACCENT_COLOR); + static Color get primary => const Color(PRIMARY_COLOR); + static Color get secondary => const Color(SECONDARY_COLOR); + static Color get splash => const Color(SPLASH_COLOR); static Color get blue => Colors.blue; static Color get orange => Colors.orange; @@ -16,15 +29,15 @@ class LSColors { static Color get blueGrey => Colors.blueGrey; static Color list(int i) { - return Constants.LIST_COLOR_ICONS[i%Constants.LIST_COLOR_ICONS.length]; + return LIST_COLOR_ICONS[i%LIST_COLOR_ICONS.length]; } static Color graph(int i) { switch(i) { - case 0: return LSColors.accent; - case 1: return LSColors.purple; - case 2: return LSColors.blue; - default: return LSColors.list(i); + case 0: return LunaColours.accent; + case 1: return LunaColours.purple; + case 2: return LunaColours.blue; + default: return LunaColours.list(i); } } } diff --git a/lib/core/ui/description_block.dart b/lib/core/ui/description_block.dart index e5b6ba4d..5273fbd6 100644 --- a/lib/core/ui/description_block.dart +++ b/lib/core/ui/description_block.dart @@ -62,7 +62,7 @@ class _State extends State { ], ), borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), - onTap: () => GlobalDialogs.textPreview(context, widget.title, widget.description.trim() ?? 'No summary is available.'), + onTap: () => LunaDialogs.textPreview(context, widget.title, widget.description.trim() ?? 'No summary is available.'), ), ); } diff --git a/lib/core/ui/dialogs.dart b/lib/core/ui/dialogs.dart index 7399325b..b0e55175 100644 --- a/lib/core/ui/dialogs.dart +++ b/lib/core/ui/dialogs.dart @@ -20,9 +20,9 @@ abstract class LSDialog { text: text, style: TextStyle( color: color == null - ? LSColors.accent + ? LunaColours.accent : color, - fontWeight: FontWeight.bold, + fontWeight: FontWeight.w600, fontSize: fontSize, ), ); @@ -42,7 +42,7 @@ abstract class LSDialog { text, style: TextStyle( color: textColor == null - ? LSColors.accent + ? LunaColours.accent : textColor, fontSize: LSDialog.BUTTON_SIZE, ), @@ -101,17 +101,17 @@ abstract class LSDialog { fontSize: LSDialog.BODY_SIZE, ), focusedBorder: UnderlineInputBorder( - borderSide: BorderSide(color: LSColors.accent), + borderSide: BorderSide(color: LunaColours.accent), ), enabledBorder: UnderlineInputBorder( - borderSide: BorderSide(color: LSColors.accent.withOpacity(0.3)), + borderSide: BorderSide(color: LunaColours.accent.withOpacity(0.3)), ), ), style: TextStyle( color: Colors.white, fontSize: LSDialog.BODY_SIZE, ), - cursorColor: LSColors.accent, + cursorColor: LunaColours.accent, textInputAction: TextInputAction.done, onSubmitted: onSubmitted, ); @@ -137,17 +137,17 @@ abstract class LSDialog { fontSize: LSDialog.BODY_SIZE, ), focusedBorder: UnderlineInputBorder( - borderSide: BorderSide(color: LSColors.accent), + borderSide: BorderSide(color: LunaColours.accent), ), enabledBorder: UnderlineInputBorder( - borderSide: BorderSide(color: LSColors.accent.withOpacity(0.3)), + borderSide: BorderSide(color: LunaColours.accent.withOpacity(0.3)), ), ), style: TextStyle( color: Colors.white, fontSize: LSDialog.BODY_SIZE, ), - cursorColor: LSColors.accent, + cursorColor: LunaColours.accent, textInputAction: TextInputAction.done, validator: validator, onFieldSubmitted: onSubmitted, @@ -165,7 +165,7 @@ abstract class LSDialog { children: [ LSIcon( icon: icon ?? Icons.error_outline, - color: iconColor ?? LSColors.accent, + color: iconColor ?? LunaColours.accent, ), ], ), @@ -202,7 +202,7 @@ abstract class LSDialog { actions: [ LSDialog.cancel( context, - textColor: buttons != null ? Colors.white : LSColors.accent, + textColor: buttons != null ? Colors.white : LunaColours.accent, ), if(buttons != null) ...buttons, ], diff --git a/lib/core/ui/divider.dart b/lib/core/ui/divider.dart index 11b6a93d..22cd2cfe 100644 --- a/lib/core/ui/divider.dart +++ b/lib/core/ui/divider.dart @@ -5,6 +5,6 @@ class LSDivider extends StatelessWidget { @override Widget build(BuildContext context) => Divider( thickness: 1.0, - color: LSColors.splash, + color: LunaColours.splash, ); } diff --git a/lib/core/ui/drawer/drawer_categories.dart b/lib/core/ui/drawer/drawer_categories.dart index cc9299dd..f83ccb72 100644 --- a/lib/core/ui/drawer/drawer_categories.dart +++ b/lib/core/ui/drawer/drawer_categories.dart @@ -127,20 +127,20 @@ class LSDrawerCategories extends StatelessWidget { return ListTile( leading: LSIcon( icon: icon, - color: currentPage ? LSColors.accent : Colors.white, + color: currentPage ? LunaColours.accent : Colors.white, ), title: Text( title, style: TextStyle( color: currentPage - ? LSColors.accent + ? LunaColours.accent : Colors.white, fontSize: Constants.UI_FONT_SIZE_SUBTITLE, ), ), onTap: () async { Navigator.of(context).pop(); - if(!currentPage) BIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(route, (Route route) => false); + if(!currentPage) LunaBIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(route, (Route route) => false); }, contentPadding: padLeft ? EdgeInsets.fromLTRB(42.0, 0.0, 0.0, 0.0) diff --git a/lib/core/ui/drawer/drawer_header.dart b/lib/core/ui/drawer/drawer_header.dart index 66746b01..41d3253b 100644 --- a/lib/core/ui/drawer/drawer_header.dart +++ b/lib/core/ui/drawer/drawer_header.dart @@ -26,16 +26,7 @@ Widget LSDrawerHeader() => UserAccountsDrawerHeader( ), ], ), - onSelected: (result) { - LunaSeaDatabaseValue.ENABLED_PROFILE.put(result); - Providers.reset(context); - LSSnackBar( - context: context, - title: 'Changed Profile', - message: 'Using profile "$result"', - type: SNACKBAR_TYPE.info, - ); - }, + onSelected: (result) => LunaProfile.changeProfile(context, result), itemBuilder: (context) { return >[for(String profile in (profilesBox as Box).keys) PopupMenuItem( value: profile, @@ -53,10 +44,10 @@ Widget LSDrawerHeader() => UserAccountsDrawerHeader( ), ), decoration: BoxDecoration( - color: LSColors.accent, + color: LunaColours.accent, image: DecorationImage( image: AssetImage('assets/branding/icon_drawer.png'), - colorFilter: ColorFilter.mode(LSColors.primary.withOpacity(0.15), BlendMode.dstATop), + colorFilter: ColorFilter.mode(LunaColours.primary.withOpacity(0.15), BlendMode.dstATop), fit: BoxFit.cover, ), ), diff --git a/lib/core/ui/drawer/drawer_nocategories.dart b/lib/core/ui/drawer/drawer_nocategories.dart index 34e27d7e..5936751a 100644 --- a/lib/core/ui/drawer/drawer_nocategories.dart +++ b/lib/core/ui/drawer/drawer_nocategories.dart @@ -93,20 +93,20 @@ class LSDrawerNoCategories extends StatelessWidget { return ListTile( leading: LSIcon( icon: icon, - color: currentPage ? LSColors.accent : Colors.white, + color: currentPage ? LunaColours.accent : Colors.white, ), title: Text( title, style: TextStyle( color: currentPage - ? LSColors.accent + ? LunaColours.accent : Colors.white, fontSize: Constants.UI_FONT_SIZE_SUBTITLE, ), ), onTap: () async { Navigator.of(context).pop(); - if(!currentPage) BIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(route, (Route route) => false); + if(!currentPage) LunaBIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(route, (Route route) => false); }, contentPadding: EdgeInsets.fromLTRB(16.0, 0.0, 0.0, 0.0), ); diff --git a/lib/core/ui/floating_action_button/floating_action_button.dart b/lib/core/ui/floating_action_button/floating_action_button.dart index 17a4a8c0..746b25d0 100644 --- a/lib/core/ui/floating_action_button/floating_action_button.dart +++ b/lib/core/ui/floating_action_button/floating_action_button.dart @@ -22,7 +22,7 @@ class LSFloatingActionButton extends StatelessWidget { heroTag: heroTag, onPressed: onPressed, backgroundColor: backgroundColor == null - ? LSColors.accent + ? LunaColours.accent : backgroundColor, ); } \ No newline at end of file diff --git a/lib/core/ui/floating_action_button/floating_action_button_animated.dart b/lib/core/ui/floating_action_button/floating_action_button_animated.dart index e121e767..2a267abf 100644 --- a/lib/core/ui/floating_action_button/floating_action_button_animated.dart +++ b/lib/core/ui/floating_action_button/floating_action_button_animated.dart @@ -28,7 +28,7 @@ class LSFloatingActionButtonAnimated extends StatelessWidget { heroTag: heroTag, onPressed: onPressed, backgroundColor: backgroundColor == null - ? LSColors.accent + ? LunaColours.accent : backgroundColor, ); } \ No newline at end of file diff --git a/lib/core/ui/floating_action_button/floating_action_button_extended.dart b/lib/core/ui/floating_action_button/floating_action_button_extended.dart index 096315cf..9879a55a 100644 --- a/lib/core/ui/floating_action_button/floating_action_button_extended.dart +++ b/lib/core/ui/floating_action_button/floating_action_button_extended.dart @@ -35,7 +35,7 @@ class LSFloatingActionButtonExtended extends StatelessWidget { heroTag: heroTag, onPressed: onPressed, backgroundColor: backgroundColor == null - ? LSColors.accent + ? LunaColours.accent : backgroundColor, ); } \ No newline at end of file diff --git a/lib/core/ui/input_bar.dart b/lib/core/ui/input_bar.dart index fd06fe00..23859a37 100644 --- a/lib/core/ui/input_bar.dart +++ b/lib/core/ui/input_bar.dart @@ -44,7 +44,7 @@ class _State extends State { child: GestureDetector( child: Icon( Icons.close, - color: LSColors.accent, + color: LunaColours.accent, size: 24.0, ), onTap: () => widget.onChanged('', true), @@ -55,7 +55,7 @@ class _State extends State { icon: Padding( child: Icon( widget.labelIcon, - color: LSColors.accent, + color: LunaColours.accent, ), padding: EdgeInsets.only(left: 16.0), ), @@ -66,7 +66,7 @@ class _State extends State { color: Colors.white, fontSize: Constants.UI_FONT_SIZE_SUBTITLE, ), - cursorColor: LSColors.accent, + cursorColor: LunaColours.accent, textInputAction: widget.action, autocorrect: false, onChanged: (value) => widget.onChanged(value, false), diff --git a/lib/core/ui/listview/listview.dart b/lib/core/ui/listview/listview.dart index b76a2d17..ee0e95f4 100644 --- a/lib/core/ui/listview/listview.dart +++ b/lib/core/ui/listview/listview.dart @@ -3,16 +3,17 @@ import 'package:flutter/material.dart'; class LSListView extends StatelessWidget { final List children; final EdgeInsetsGeometry customPadding; - final ScrollController controller = ScrollController(); + final ScrollController controller; LSListView({ @required this.children, + this.controller, this.customPadding = const EdgeInsets.symmetric(vertical: 8.0), }); @override Widget build(BuildContext context) => Scrollbar( - controller: controller, + controller: controller == null ? ScrollController() : controller, child: ListView( controller: controller, children: children, diff --git a/lib/core/ui/loader.dart b/lib/core/ui/loader.dart index 2fab1511..63d0e569 100644 --- a/lib/core/ui/loader.dart +++ b/lib/core/ui/loader.dart @@ -18,7 +18,7 @@ class LSLoader extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ SpinKitThreeBounce( - color: color != null ? color : LSColors.accent, + color: color != null ? color : LunaColours.accent, size: size, ), ], diff --git a/lib/core/ui/navigation_bar/navigation_bar.dart b/lib/core/ui/navigation_bar/navigation_bar.dart index 163435da..dd29425d 100644 --- a/lib/core/ui/navigation_bar/navigation_bar.dart +++ b/lib/core/ui/navigation_bar/navigation_bar.dart @@ -26,7 +26,7 @@ class LSBottomNavigationBar extends StatelessWidget { padding: EdgeInsets.fromLTRB(18.0, 5.0, 12.0, 5.0), duration: Duration(milliseconds: Constants.UI_NAVIGATION_SPEED), tabBackgroundColor: Theme.of(context).canvasColor, - activeColor: LSColors.accent, + activeColor: LunaColours.accent, tabs: List.generate( icons.length, (index) => GButton( @@ -36,7 +36,7 @@ class LSBottomNavigationBar extends StatelessWidget { textStyle: TextStyle( fontWeight: FontWeight.w600, fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - color: LSColors.accent, + color: LunaColours.accent, ), ) ).toList(), @@ -48,7 +48,7 @@ class LSBottomNavigationBar extends StatelessWidget { ), decoration: BoxDecoration( color: Theme.of(context).primaryColor, - //LSColors.secondary, + //LunaColours.secondary, ), ); } diff --git a/lib/core/ui/snackbar.dart b/lib/core/ui/snackbar.dart index 1ada285a..ff024d34 100644 --- a/lib/core/ui/snackbar.dart +++ b/lib/core/ui/snackbar.dart @@ -25,9 +25,9 @@ Future LSSnackBar({ Color color; IconData icon; switch(type) { - case SNACKBAR_TYPE.failure: color = LSColors.red; icon = Icons.error_outline; break; - 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; + case SNACKBAR_TYPE.failure: color = LunaColours.red; icon = Icons.error_outline; break; + case SNACKBAR_TYPE.success: color = LunaColours.accent; icon = Icons.check_circle_outline; break; + case SNACKBAR_TYPE.info: color = LunaColours.blue; icon = Icons.info_outline; break; } showFlash( context: context, @@ -77,7 +77,7 @@ Future LSSnackBar({ fontWeight: FontWeight.bold, ), ), - textColor: LSColors.accent, + textColor: LunaColours.accent, onPressed: () { controller.dismiss(); buttonOnPressed(); diff --git a/lib/core/ui/table/block.dart b/lib/core/ui/table/block.dart index c716474e..e09c418a 100644 --- a/lib/core/ui/table/block.dart +++ b/lib/core/ui/table/block.dart @@ -3,7 +3,7 @@ import 'package:lunasea/core.dart'; class LSTableBlock extends StatelessWidget { final String title; - final List children; + final List children; LSTableBlock({ Key key, @@ -13,7 +13,7 @@ class LSTableBlock extends StatelessWidget { List _block({ String title, - @required List children, + @required List children, }) => [ if(title != null) LSHeader(text: title), LSCard( diff --git a/lib/core/ui/table/content.dart b/lib/core/ui/table/content.dart index 699aeb8b..de2eef67 100644 --- a/lib/core/ui/table/content.dart +++ b/lib/core/ui/table/content.dart @@ -4,11 +4,25 @@ import 'package:lunasea/core.dart'; class LSTableContent extends StatelessWidget { final String title; final String body; + final TextAlign titleAlign; + final TextAlign bodyAlign; + final int titleFlex; + final int bodyFlex; + final EdgeInsets padding; + final Color titleColour; + final Color bodyColour; LSTableContent({ Key key, @required this.title, @required this.body, + this.titleAlign = TextAlign.end, + this.bodyAlign = TextAlign.start, + this.titleFlex = 2, + this.bodyFlex = 5, + this.padding = const EdgeInsets.symmetric(horizontal: 12.0, vertical: 4.0), + this.titleColour = Colors.white70, + this.bodyColour = Colors.white, }) : super(key: key); @override @@ -18,28 +32,29 @@ class LSTableContent extends StatelessWidget { Expanded( child: Text( title.toUpperCase(), - textAlign: TextAlign.end, + textAlign: titleAlign, style: TextStyle( - color: Colors.white70, + color: titleColour, fontSize: Constants.UI_FONT_SIZE_SUBTITLE, ), ), - flex: 2, + flex: titleFlex, ), Container(width: 16.0, height: 0.0), Expanded( child: Text( body, - textAlign: TextAlign.start, + textAlign: bodyAlign, style: TextStyle( + color: bodyColour, fontSize: Constants.UI_FONT_SIZE_SUBTITLE, ), ), - flex: 5, + flex: bodyFlex, ), ], crossAxisAlignment: CrossAxisAlignment.start, ), - padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 4.0), + padding: padding, ); } \ No newline at end of file diff --git a/lib/core/ui/text/header.dart b/lib/core/ui/text/header.dart index 6780c08f..ef11e77a 100644 --- a/lib/core/ui/text/header.dart +++ b/lib/core/ui/text/header.dart @@ -30,7 +30,7 @@ class LSHeader extends StatelessWidget { width: 48.0, decoration: BoxDecoration( borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), - color: LSColors.accent, + color: LunaColours.accent, ), ), padding: EdgeInsets.only( diff --git a/lib/core/ui/text/text_highlighted.dart b/lib/core/ui/text/text_highlighted.dart index 02ba7a0f..c5861c45 100644 --- a/lib/core/ui/text/text_highlighted.dart +++ b/lib/core/ui/text/text_highlighted.dart @@ -9,7 +9,7 @@ class LSTextHighlighted extends StatelessWidget { LSTextHighlighted({ @required this.text, - this.bgColor = const Color(Constants.ACCENT_COLOR), + this.bgColor = const Color(LunaColours.ACCENT_COLOR), this.fgColor = Colors.white, this.margin = const EdgeInsets.only(right: 8.0), }); diff --git a/lib/core/uuid.dart b/lib/core/uuid.dart index b6d1978f..b2af6d3e 100644 --- a/lib/core/uuid.dart +++ b/lib/core/uuid.dart @@ -1,6 +1,6 @@ import 'package:uuid/uuid.dart'; -class UUID { +class LunaUUID { static final _generator = Uuid(); static String get uuid => _generator.v4(); } diff --git a/lib/main.dart b/lib/main.dart index 63ba3061..ee4af4f9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,8 +8,8 @@ import 'package:lunasea/core.dart'; void main() async { await _init(); runZonedGuarded( - () => runApp(BIOS()), - (Object error, StackTrace stack) => Logger.fatal(error, stack), + () => runApp(LunaBIOS()), + (Object error, StackTrace stack) => LunaLogger.fatal(error, stack), ); } @@ -22,33 +22,34 @@ Future _init() async { statusBarColor: Colors.transparent, )); //LunaSea initialization - Logger.initialize(); + LunaNetworking.initialize(); + LunaLogger.initialize(); LunaImageCache.initialize(); LunaRouter.intialize(); - await InAppPurchases.initialize(); + await LunaInAppPurchases.initialize(); await Database.initialize(); } -class BIOS extends StatefulWidget { +class LunaBIOS extends StatefulWidget { static final GlobalKey navigatorKey = GlobalKey(); @override State createState() => _State(); } -class _State extends State { +class _State extends State { @override - Widget build(BuildContext context) => Providers.providers( + Widget build(BuildContext context) => LunaProvider.providers( child: ValueListenableBuilder( valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.THEME_AMOLED.key]), builder: (context, box, _) { return MaterialApp( - navigatorKey: BIOS.navigatorKey, - title: Constants.APPLICATION_NAME, - debugShowCheckedModeBanner: false, + navigatorKey: LunaBIOS.navigatorKey, routes: LunaRouter.routes, - darkTheme: Themes.getDarkTheme(), - theme: Themes.getDarkTheme(), + onGenerateRoute: LunaRouter.router.generator, + darkTheme: LunaTheme.darkTheme, + theme: LunaTheme.darkTheme, + title: Constants.APPLICATION_NAME, ); } ), @@ -57,7 +58,7 @@ class _State extends State { @override void dispose() { Database.deinitialize() - .whenComplete(() => InAppPurchases.deinitialize()) + .whenComplete(() => LunaInAppPurchases.deinitialize()) .whenComplete(() => super.dispose()); } } diff --git a/lib/modules.dart b/lib/modules.dart index 201a899f..60a81b7d 100644 --- a/lib/modules.dart +++ b/lib/modules.dart @@ -1,6 +1,7 @@ export 'modules/home.dart'; export 'modules/lidarr.dart'; export 'modules/nzbget.dart'; +export 'modules/ombi.dart'; export 'modules/radarr.dart'; export 'modules/sabnzbd.dart'; export 'modules/search.dart'; diff --git a/lib/modules/home/core/api/api.dart b/lib/modules/home/core/api/api.dart index 7b20884a..9c07de90 100644 --- a/lib/modules/home/core/api/api.dart +++ b/lib/modules/home/core/api/api.dart @@ -1,6 +1,3 @@ -import 'dart:io'; - -import 'package:dio/adapter.dart'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -28,7 +25,7 @@ class CalendarAPI extends API { void logError(String methodName, String text, Object error, StackTrace trace, { bool uploadToSentry = true, - }) => Logger.error( + }) => LunaLogger.error( 'package:lunasea/core/api/calendar/api.dart', methodName, 'Home: $text', @@ -37,7 +34,7 @@ class CalendarAPI extends API { uploadToSentry: uploadToSentry, ); - void logWarning(String methodName, String text) => Logger.warning( + void logWarning(String methodName, String text) => LunaLogger.warning( 'package:lunasea/core/api/calendar/api.dart', methodName, 'Home: $text', @@ -80,11 +77,6 @@ class CalendarAPI extends API { 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('calendar'); if(response.data.length > 0) { for(var entry in response.data) { @@ -126,11 +118,6 @@ class CalendarAPI extends API { 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('calendar'); if(response.data.length > 0) { for(var entry in response.data) { @@ -173,11 +160,6 @@ class CalendarAPI extends API { 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('calendar'); if(response.data.length > 0) { for(var entry in response.data) { diff --git a/lib/modules/home/core/api/data/lidarr.dart b/lib/modules/home/core/api/data/lidarr.dart index b7541769..cba6a775 100644 --- a/lib/modules/home/core/api/data/lidarr.dart +++ b/lib/modules/home/core/api/data/lidarr.dart @@ -48,7 +48,7 @@ class CalendarLidarrData extends CalendarData { text: '\nDownloaded', style: TextStyle( fontWeight: FontWeight.bold, - color: LSColors.accent, + color: LunaColours.accent, ), ) ], diff --git a/lib/modules/home/core/api/data/radarr.dart b/lib/modules/home/core/api/data/radarr.dart index 4e5983ce..05aef824 100644 --- a/lib/modules/home/core/api/data/radarr.dart +++ b/lib/modules/home/core/api/data/radarr.dart @@ -43,7 +43,7 @@ class CalendarRadarrData extends CalendarData { text: '\nDownloaded ($fileQualityProfile)', style: TextStyle( fontWeight: FontWeight.bold, - color: LSColors.accent, + color: LunaColours.accent, ), ) ], diff --git a/lib/modules/home/core/api/data/sonarr.dart b/lib/modules/home/core/api/data/sonarr.dart index 87ef3acb..f223a08e 100644 --- a/lib/modules/home/core/api/data/sonarr.dart +++ b/lib/modules/home/core/api/data/sonarr.dart @@ -52,7 +52,7 @@ class CalendarSonarrData extends CalendarData { text: '\nDownloaded ($fileQualityProfile)', style: TextStyle( fontWeight: FontWeight.bold, - color: LSColors.accent, + color: LunaColours.accent, ), ) ], @@ -66,12 +66,9 @@ class CalendarSonarrData extends CalendarData { : ''; } - Future enterContent(BuildContext context) async => Navigator.of(context).pushNamed( - SonarrDetailsSeries.ROUTE_NAME, - arguments: SonarrDetailsSeriesArguments( - data: null, - seriesID: seriesID, - ), + Future enterContent(BuildContext context) async => SonarrSeriesDetailsRouter.navigateTo( + context, + seriesId: seriesID, ); Widget trailing(BuildContext context) => InkWell( @@ -98,23 +95,40 @@ class CalendarSonarrData extends CalendarData { if(airTimeObject != null) { return LunaSeaDatabaseValue.USE_24_HOUR_TIME.data ? DateFormat.Hm().format(airTimeObject) - : DateFormat('KK:mm\na').format(airTimeObject); + : DateFormat('hh:mm\na').format(airTimeObject); } return 'Unknown'; } @override Future trailingOnPress(BuildContext context) async { - await SonarrAPI.from(Database.currentProfileObject).searchEpisodes([id]) - .then((_) => LSSnackBar(context: context, title: 'Searching...', message: episodeTitle)) - .catchError((_) => LSSnackBar(context: context, title: 'Failed to Search', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure)); + if(context.read().api != null) context.read().api.command.episodeSearch(episodeIds: [id]) + .then((_) => LSSnackBar( + context: context, + title: 'Searching for Episode...', + message: episodeTitle, + type: SNACKBAR_TYPE.success, + )) + .catchError((error, stack) { + LunaLogger.error( + 'CalendarSonarrData', + 'trailingOnPress', + 'Failed to search for episode: $id', + error, + stack, + uploadToSentry: !(error is DioError), + ); + LSSnackBar( + context: context, + title: 'Failed to Search', + type: SNACKBAR_TYPE.failure, + ); + }); } + @override - Future trailingOnLongPress(BuildContext context) async => Navigator.of(context).pushNamed( - SonarrSearchResults.ROUTE_NAME, - arguments: SonarrSearchResultsArguments( - episodeID: id, - title: episodeTitle, - ), + Future trailingOnLongPress(BuildContext context) async => SonarrReleasesRouter.navigateTo( + context, + episodeId: id, ); } diff --git a/lib/modules/home/core/constants.dart b/lib/modules/home/core/constants.dart index 314842a2..7b22cd25 100644 --- a/lib/modules/home/core/constants.dart +++ b/lib/modules/home/core/constants.dart @@ -6,13 +6,13 @@ class HomeConstants { static const MODULE_KEY = 'home'; - static const ModuleMap MODULE_MAP = ModuleMap( + static const LunaModuleMap MODULE_MAP = LunaModuleMap( name: 'Home', description: 'Home', settingsDescription: 'Configure the Home Screen', icon: CustomIcons.home, route: '/', - color: Color(Constants.ACCENT_COLOR), + color: Color(LunaColours.ACCENT_COLOR), ); //ignore: non_constant_identifier_names diff --git a/lib/modules/home/core/dialogs.dart b/lib/modules/home/core/dialogs.dart index 74f4d793..1326681f 100644 --- a/lib/modules/home/core/dialogs.dart +++ b/lib/modules/home/core/dialogs.dart @@ -23,7 +23,7 @@ class HomeDialogs { (index) => LSDialog.tile( text: HomeNavigationBar.titles[index], icon: HomeNavigationBar.icons[index], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, index), ), ), diff --git a/lib/modules/home/core/state.dart b/lib/modules/home/core/state.dart index c33dc715..ed5ebd8f 100644 --- a/lib/modules/home/core/state.dart +++ b/lib/modules/home/core/state.dart @@ -1,36 +1,2 @@ -import 'package:flutter/foundation.dart'; -import 'package:lunasea/modules/home.dart'; - -class HomeState extends ChangeNotifier { - HomeState() { - reset(initialize: true); - } - - /// Reset the state of Home back to the default - /// - /// If `initialize` is true, resets everything. - /// If false, the navigation index, etc. are not reset. - void reset({ bool initialize = false }) { - if(initialize) { - _navigationIndex = HomeDatabaseValue.NAVIGATION_INDEX.data; - _calendarStartingType = HomeDatabaseValue.CALENDAR_STARTING_TYPE.data; - } - notifyListeners(); - } - - int _navigationIndex; - int get navigationIndex => _navigationIndex; - set navigationIndex(int navigationIndex) { - assert(navigationIndex != null); - _navigationIndex = navigationIndex; - notifyListeners(); - } - - CalendarStartingType _calendarStartingType; - CalendarStartingType get calendarStartingType => _calendarStartingType; - set calendarStartingType(CalendarStartingType calendarStartingType) { - assert(calendarStartingType != null); - _calendarStartingType = calendarStartingType; - notifyListeners(); - } -} +export 'state/global.dart'; +export 'state/local.dart'; diff --git a/lib/modules/home/core/state/global.dart b/lib/modules/home/core/state/global.dart new file mode 100644 index 00000000..edd5e420 --- /dev/null +++ b/lib/modules/home/core/state/global.dart @@ -0,0 +1,23 @@ +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/home.dart'; + +class HomeState extends LunaGlobalState { + @override + void reset() {} + + int _navigationIndex = HomeDatabaseValue.NAVIGATION_INDEX.data; + int get navigationIndex => _navigationIndex; + set navigationIndex(int navigationIndex) { + assert(navigationIndex != null); + _navigationIndex = navigationIndex; + notifyListeners(); + } + + CalendarStartingType _calendarStartingType = HomeDatabaseValue.CALENDAR_STARTING_TYPE.data; + CalendarStartingType get calendarStartingType => _calendarStartingType; + set calendarStartingType(CalendarStartingType calendarStartingType) { + assert(calendarStartingType != null); + _calendarStartingType = calendarStartingType; + notifyListeners(); + } +} diff --git a/lib/modules/home/core/state/local.dart b/lib/modules/home/core/state/local.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/modules/home/routes/home.dart b/lib/modules/home/routes/home.dart index 290fe1bc..4670f8c8 100644 --- a/lib/modules/home/routes/home.dart +++ b/lib/modules/home/routes/home.dart @@ -19,7 +19,7 @@ class _State extends State { @override void initState() { super.initState(); - HomescreenActions.initialize(context); + LunaQuickActions.initialize(context); } @override @@ -56,8 +56,11 @@ class _State extends State { Widget get _drawer => LSDrawer(page: 'home'); - Widget get _appBar => LSAppBar( + Widget get _appBar => LunaAppBar( + context: context, title: Constants.APPLICATION_NAME, + popUntil: null, + hideLeading: true, actions: Database.currentProfileObject.anyAutomationEnabled ? [ Selector>( diff --git a/lib/modules/home/routes/quick_access.dart b/lib/modules/home/routes/quick_access.dart index b8a9f43f..b7b35712 100644 --- a/lib/modules/home/routes/quick_access.dart +++ b/lib/modules/home/routes/quick_access.dart @@ -49,7 +49,7 @@ class _State extends State with AutomaticKeepAliveClientMixin { route: Constants.MODULE_MAP[SettingsConstants.MODULE_KEY].route, color: Constants.MODULE_MAP[SettingsConstants.MODULE_KEY].color, ); - ModuleMap data = Constants.MODULE_MAP[widget.profile.enabledModules[_hasIndexers ? index-1 : index]]; + LunaModuleMap data = Constants.MODULE_MAP[widget.profile.enabledModules[_hasIndexers ? index-1 : index]]; if(data != null) return HomeSummaryTile( title: data.name, subtitle: data.description, diff --git a/lib/modules/home/widgets/calendar.dart b/lib/modules/home/widgets/calendar.dart index ebe57614..92ee2f51 100644 --- a/lib/modules/home/widgets/calendar.dart +++ b/lib/modules/home/widgets/calendar.dart @@ -30,7 +30,7 @@ class _State extends State with TickerProviderStateMixin { fontSize: Constants.UI_FONT_SIZE_SUBTITLE, ); final TextStyle weekdayTitleStyle = TextStyle( - color: LSColors.accent, + color: LunaColours.accent, fontWeight: FontWeight.bold, fontSize: Constants.UI_FONT_SIZE_SUBTITLE, ); @@ -69,7 +69,6 @@ class _State extends State with TickerProviderStateMixin { child: Column( children: [ _calendar, - LSDivider(), _list, ], ), @@ -92,9 +91,9 @@ class _State extends State with TickerProviderStateMixin { events: widget.events, startingDayOfWeek: (HomeDatabaseValue.CALENDAR_STARTING_DAY.data as CalendarStartingDay).data, calendarStyle: CalendarStyle( - selectedColor: LSColors.accent.withOpacity(0.25), + selectedColor: LunaColours.accent.withOpacity(0.25), markersMaxAmount: 1, - markersColor: LSColors.accent, + markersColor: LunaColours.accent, weekendStyle: dayTileStyle, weekdayStyle: dayTileStyle, outsideStyle: outsideDayTileStyle, @@ -102,7 +101,7 @@ class _State extends State with TickerProviderStateMixin { outsideWeekendStyle: outsideDayTileStyle, renderDaysOfWeek: true, highlightToday: true, - todayColor: LSColors.primary, + todayColor: LunaColours.primary, todayStyle: dayTileStyle, outsideDaysVisible: false, ), diff --git a/lib/modules/home/widgets/summary_tile.dart b/lib/modules/home/widgets/summary_tile.dart index 22dd0646..6f146a78 100644 --- a/lib/modules/home/widgets/summary_tile.dart +++ b/lib/modules/home/widgets/summary_tile.dart @@ -30,9 +30,9 @@ class HomeSummaryTile extends StatelessWidget { icon: icon, color: HomeDatabaseValue.MODULES_BRAND_COLOURS.data ? color - : LSColors.list(index), + : LunaColours.list(index), ), - onTap: () async => BIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(route, (Route route) => false), + onTap: () async => LunaBIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(route, (Route route) => false), ), ); } \ No newline at end of file diff --git a/lib/modules/lidarr/core.dart b/lib/modules/lidarr/core.dart index 86fb2adc..0a55df2c 100644 --- a/lib/modules/lidarr/core.dart +++ b/lib/modules/lidarr/core.dart @@ -2,5 +2,5 @@ export 'core/api.dart'; export 'core/constants.dart'; export 'core/database.dart'; export 'core/dialogs.dart'; -export 'core/state_global.dart'; +export 'core/state.dart'; export 'core/sorting.dart'; diff --git a/lib/modules/lidarr/core/api/api.dart b/lib/modules/lidarr/core/api/api.dart index c802021c..8d9e75e5 100644 --- a/lib/modules/lidarr/core/api/api.dart +++ b/lib/modules/lidarr/core/api/api.dart @@ -1,6 +1,4 @@ -import 'dart:io'; import 'dart:convert'; -import 'package:dio/adapter.dart'; import 'package:dio/dio.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/lidarr.dart'; @@ -25,18 +23,13 @@ class LidarrAPI extends API { maxRedirects: 5, ), ); - if(!profile.getLidarr()['strict_tls']) { - (_client.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) { - client.badCertificateCallback = (X509Certificate cert, String host, int port) => true; - }; - } return LidarrAPI._internal( profile.getLidarr(), _client, ); } - void logWarning(String methodName, String text) => Logger.warning( + void logWarning(String methodName, String text) => LunaLogger.warning( 'package:lunasea/core/api/lidarr/api.dart', methodName, 'Lidarr: $text', @@ -44,7 +37,7 @@ class LidarrAPI extends API { void logError(String methodName, String text, Object error, StackTrace trace, { bool uploadToSentry = true, - }) => Logger.error( + }) => LunaLogger.error( 'package:lunasea/core/api/lidarr/api.dart', methodName, 'Lidarr: $text', diff --git a/lib/modules/lidarr/core/api/data/history.dart b/lib/modules/lidarr/core/api/data/history.dart index 9b6b2de7..15a045c3 100644 --- a/lib/modules/lidarr/core/api/data/history.dart +++ b/lib/modules/lidarr/core/api/data/history.dart @@ -57,7 +57,7 @@ class LidarrHistoryDataGeneric extends LidarrHistoryData { TextSpan( text: '$eventType', style: TextStyle( - color: LSColors.purple, + color: LunaColours.purple, fontWeight: FontWeight.bold, ), ), @@ -111,7 +111,7 @@ class LidarrHistoryDataTrackFileImported extends LidarrHistoryData { TextSpan( text: '${LidarrConstants.EVENT_TYPE_MESSAGES[eventType]} ($quality)', style: TextStyle( - color: Color(Constants.ACCENT_COLOR), + color: Color(LunaColours.ACCENT_COLOR), fontWeight: FontWeight.bold, ), ) @@ -138,7 +138,7 @@ class LidarrHistoryDataDownloadImported extends LidarrHistoryData { TextSpan( text: '${LidarrConstants.EVENT_TYPE_MESSAGES[eventType]} ($quality)', style: TextStyle( - color: Color(Constants.ACCENT_COLOR), + color: Color(LunaColours.ACCENT_COLOR), fontWeight: FontWeight.bold, ), ) @@ -213,7 +213,7 @@ class LidarrHistoryDataTrackFileRenamed extends LidarrHistoryData { TextSpan( text: '${LidarrConstants.EVENT_TYPE_MESSAGES[eventType]}', style: TextStyle( - color: Color(Constants.ACCENT_COLOR), + color: Color(LunaColours.ACCENT_COLOR), fontWeight: FontWeight.bold, ), ) diff --git a/lib/modules/lidarr/core/api/data/track.dart b/lib/modules/lidarr/core/api/data/track.dart index 400d4ee4..808e43fc 100644 --- a/lib/modules/lidarr/core/api/data/track.dart +++ b/lib/modules/lidarr/core/api/data/track.dart @@ -23,7 +23,7 @@ class LidarrTrackData { return TextSpan( text: 'Downloaded', style: TextStyle( - color: monitored ? Color(Constants.ACCENT_COLOR) : Color(Constants.ACCENT_COLOR).withOpacity(0.30), + color: monitored ? Color(LunaColours.ACCENT_COLOR) : Color(LunaColours.ACCENT_COLOR).withOpacity(0.30), fontWeight: FontWeight.bold, ), ); diff --git a/lib/modules/lidarr/core/constants.dart b/lib/modules/lidarr/core/constants.dart index a0d4d81e..a452f989 100644 --- a/lib/modules/lidarr/core/constants.dart +++ b/lib/modules/lidarr/core/constants.dart @@ -6,7 +6,7 @@ class LidarrConstants { static const MODULE_KEY = 'lidarr'; - static const ModuleMap MODULE_MAP = ModuleMap( + static const LunaModuleMap MODULE_MAP = LunaModuleMap( name: 'Lidarr', description: 'Manage Music', settingsDescription: 'Configure Lidarr', diff --git a/lib/modules/lidarr/core/dialogs.dart b/lib/modules/lidarr/core/dialogs.dart index 68b61acd..809801d3 100644 --- a/lib/modules/lidarr/core/dialogs.dart +++ b/lib/modules/lidarr/core/dialogs.dart @@ -22,7 +22,7 @@ class LidarrDialogs { qualities.length, (index) => LSDialog.tile( icon: Icons.portrait, - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), text: qualities[index].name, onTap: () => _setValues(true, qualities[index]), ), @@ -49,7 +49,7 @@ class LidarrDialogs { metadatas.length, (index) => LSDialog.tile( icon: Icons.portrait, - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), text: metadatas[index].name, onTap: () => _setValues(true, metadatas[index]), ), @@ -75,12 +75,12 @@ class LidarrDialogs { buttons: [ LSDialog.button( text: 'Remove + Files', - textColor: LSColors.red, + textColor: LunaColours.red, onPressed: () => _setValues(true, true), ), LSDialog.button( text: 'Remove', - textColor: LSColors.red, + textColor: LunaColours.red, onPressed: () => _setValues(true, false), ), ], @@ -164,7 +164,7 @@ class LidarrDialogs { _options.length, (index) => LSDialog.tile( icon: _options[index][1], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), text: _options[index][0], onTap: () => _setValues(true, _options[index][2]), ), @@ -197,7 +197,7 @@ class LidarrDialogs { ], ), icon: Icons.folder, - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, folders[index]), ), ), @@ -231,7 +231,7 @@ class LidarrDialogs { (index) => LSDialog.tile( text: _options[index][0], icon: _options[index][1], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, _options[index][2]), ), ), @@ -258,7 +258,7 @@ class LidarrDialogs { (index) => LSDialog.tile( text: LidarrNavigationBar.titles[index], icon: LidarrNavigationBar.icons[index], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, index), ), ), diff --git a/lib/modules/lidarr/core/sorting/catalogue.dart b/lib/modules/lidarr/core/sorting/catalogue.dart index a1013762..20b4a624 100644 --- a/lib/modules/lidarr/core/sorting/catalogue.dart +++ b/lib/modules/lidarr/core/sorting/catalogue.dart @@ -46,7 +46,7 @@ extension LidarrCatalogueSortingExtension on LidarrCatalogueSorting { ) => _sorter.byType(data, this, ascending); } -class _Sorter extends Sorter { +class _Sorter extends LunaSorter { @override List byType( List data, diff --git a/lib/modules/lidarr/core/sorting/releases.dart b/lib/modules/lidarr/core/sorting/releases.dart index 46b26639..6f70594a 100644 --- a/lib/modules/lidarr/core/sorting/releases.dart +++ b/lib/modules/lidarr/core/sorting/releases.dart @@ -43,7 +43,7 @@ extension LidarrReleasesSortingExtension on LidarrReleasesSorting { ) => _sorter.byType(data, this, ascending); } -class _Sorter extends Sorter { +class _Sorter extends LunaSorter { @override List byType( List data, diff --git a/lib/modules/lidarr/core/state.dart b/lib/modules/lidarr/core/state.dart new file mode 100644 index 00000000..ed5ebd8f --- /dev/null +++ b/lib/modules/lidarr/core/state.dart @@ -0,0 +1,2 @@ +export 'state/global.dart'; +export 'state/local.dart'; diff --git a/lib/modules/lidarr/core/state_global.dart b/lib/modules/lidarr/core/state/global.dart similarity index 95% rename from lib/modules/lidarr/core/state_global.dart rename to lib/modules/lidarr/core/state/global.dart index d06a246e..c44529eb 100644 --- a/lib/modules/lidarr/core/state_global.dart +++ b/lib/modules/lidarr/core/state/global.dart @@ -1,9 +1,15 @@ -import 'package:flutter/foundation.dart'; +import 'package:lunasea/core.dart'; import 'package:lunasea/modules/lidarr.dart'; -class LidarrModel extends ChangeNotifier { - ///Catalogue Sticky Header Content +class LidarrState extends LunaGlobalState { + LidarrState() { + reset(); + } + @override + void reset() {} + + ///Catalogue Sticky Header Content String _searchCatalogueFilter = ''; String get searchCatalogueFilter => _searchCatalogueFilter; set searchCatalogueFilter(String searchCatalogueFilter) { diff --git a/lib/modules/lidarr/core/state/local.dart b/lib/modules/lidarr/core/state/local.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/modules/lidarr/routes/add_details.dart b/lib/modules/lidarr/routes/add_details.dart index d5df5fb7..d4df3215 100644 --- a/lib/modules/lidarr/routes/add_details.dart +++ b/lib/modules/lidarr/routes/add_details.dart @@ -101,8 +101,10 @@ class _State extends State { Widget get _appBar => _arguments == null ? null - : LSAppBar( + : LunaAppBar( + context: context, title: _arguments.data.title, + popUntil: '/lidarr', actions: [ LSIconButton( icon: Icons.link, @@ -147,7 +149,6 @@ class _State extends State { squareImage: true, headers: Database.currentProfileObject.getLidarr()['headers'], ), - LSDivider(), ValueListenableBuilder( valueListenable: Database.lunaSeaBox.listenable(keys: [LidarrDatabaseValue.ADD_MONITORED.key]), builder: (context, box, widget) { @@ -219,7 +220,6 @@ class _State extends State { ); }, ), - LSDivider(), LSContainerRow( children: [ Expanded( @@ -232,7 +232,7 @@ class _State extends State { Expanded( child: LSButton( text: 'Add + Search', - backgroundColor: LSColors.orange, + backgroundColor: LunaColours.orange, onTap: () async => _addArtist(true), reducedMargin: true, ), diff --git a/lib/modules/lidarr/routes/add_search.dart b/lib/modules/lidarr/routes/add_search.dart index 3b9a7d18..69afca08 100644 --- a/lib/modules/lidarr/routes/add_search.dart +++ b/lib/modules/lidarr/routes/add_search.dart @@ -31,7 +31,7 @@ class _State extends State { ); Future _refresh() async { - final _model = Provider.of(context, listen: false); + final _model = Provider.of(context, listen: false); final _api = LidarrAPI.from(Database.currentProfileObject); setState(() { _future = _api.searchArtists(_model.addSearchQuery); @@ -44,7 +44,11 @@ class _State extends State { .catchError((_) => _availableIDs = []); } - Widget get _appBar => LSAppBar(title: 'Add Artist'); + Widget get _appBar => LunaAppBar( + context: context, + title: 'Add Artist', + popUntil: '/lidarr', + ); Widget get _body => LSRefreshIndicator( refreshKey: _refreshKey, diff --git a/lib/modules/lidarr/routes/catalogue.dart b/lib/modules/lidarr/routes/catalogue.dart index 6e3b4796..08106b77 100644 --- a/lib/modules/lidarr/routes/catalogue.dart +++ b/lib/modules/lidarr/routes/catalogue.dart @@ -37,7 +37,7 @@ class _State extends State with AutomaticKeepAliveClientMixin { final _api = LidarrAPI.from(Database.currentProfileObject); if(mounted) setState(() => { _future = _api.getAllArtists() }); //Clear the search filter using a microtask - Future.microtask(() => Provider.of(context, listen: false)?.searchCatalogueFilter = ''); + Future.microtask(() => Provider.of(context, listen: false)?.searchCatalogueFilter = ''); } void _refreshState() => setState(() {}); @@ -91,7 +91,7 @@ class _State extends State with AutomaticKeepAliveClientMixin { buttonText: 'Refresh', onTapHandler: () => _refresh(), ) - : Consumer( + : Consumer( builder: (context, model, widget) { //Filter and sort the results List _filtered = _sort(model, _filter(model.searchCatalogueFilter)); @@ -129,8 +129,8 @@ class _State extends State with AutomaticKeepAliveClientMixin { : entry.title.toLowerCase().contains(filter.toLowerCase()) ).toList(); - List _sort(LidarrModel model, List data) { - if(data != null && data.length != 0) return model.sortCatalogueType.sort(data, model.sortCatalogueAscending); + List _sort(LidarrState state, List data) { + if(data != null && data.length != 0) return state.sortCatalogueType.sort(data, state.sortCatalogueAscending); return data; } diff --git a/lib/modules/lidarr/routes/details_album.dart b/lib/modules/lidarr/routes/details_album.dart index 6ef68225..19cc5c6e 100644 --- a/lib/modules/lidarr/routes/details_album.dart +++ b/lib/modules/lidarr/routes/details_album.dart @@ -53,7 +53,9 @@ class _State extends State { appBar: _appBar, ); - Widget get _appBar => LSAppBar( + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/lidarr', title: _arguments == null ? 'Details Album' : _arguments.title, actions: [ InkWell( diff --git a/lib/modules/lidarr/routes/details_artist.dart b/lib/modules/lidarr/routes/details_artist.dart index bce9e424..135de502 100644 --- a/lib/modules/lidarr/routes/details_artist.dart +++ b/lib/modules/lidarr/routes/details_artist.dart @@ -31,7 +31,7 @@ class _State extends State { super.initState(); SchedulerBinding.instance.addPostFrameCallback((_) { _arguments = ModalRoute.of(context).settings.arguments; - Provider.of(context, listen: false).artistNavigationIndex = 1; + Provider.of(context, listen: false).artistNavigationIndex = 1; _fetch(); }); } @@ -65,7 +65,9 @@ class _State extends State { : null, ); - Widget get _appBar => LSAppBar( + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/lidarr', title: _arguments == null || _arguments.data == null ? 'Artist Details' : _arguments.data.title, @@ -93,7 +95,7 @@ class _State extends State { onPageChanged: _onPageChanged, ); - void _onPageChanged(int index) => Provider.of(context, listen: false).artistNavigationIndex = index; + void _onPageChanged(int index) => Provider.of(context, listen: false).artistNavigationIndex = index; Future _removeCallback(bool withData) async => Navigator.of(context).pop(['remove_artist', withData]); } diff --git a/lib/modules/lidarr/routes/edit_artist.dart b/lib/modules/lidarr/routes/edit_artist.dart index c4deb36c..7a8d04f8 100644 --- a/lib/modules/lidarr/routes/edit_artist.dart +++ b/lib/modules/lidarr/routes/edit_artist.dart @@ -93,7 +93,11 @@ class _State extends State { .catchError((error) => Future.error(error)); } - Widget get _appBar => LSAppBar(title: _arguments?.entry?.title ?? 'Edit Artist'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/lidarr', + title: _arguments?.entry?.title ?? 'Edit Artist', + ); Widget get _body => FutureBuilder( future: _future, @@ -147,7 +151,6 @@ class _State extends State { trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () => _changeMetadata(), ), - LSDivider(), LSButton( text: 'Update Artist', onTap: () async => _save().catchError((_) {}), @@ -156,7 +159,7 @@ class _State extends State { ); Future _changePath() async { - List _values = await GlobalDialogs.editText(context, 'Artist Path', prefill: _path); + List _values = await LunaDialogs.editText(context, 'Artist Path', prefill: _path); if(_values[0] && mounted) setState(() => _path = _values[1]); } diff --git a/lib/modules/lidarr/routes/lidarr.dart b/lib/modules/lidarr/routes/lidarr.dart index 6b8cd034..6c002af0 100644 --- a/lib/modules/lidarr/routes/lidarr.dart +++ b/lib/modules/lidarr/routes/lidarr.dart @@ -24,7 +24,7 @@ class _State extends State { @override void initState() { super.initState(); - Future.microtask(() => Provider.of(context, listen: false).navigationIndex = 0); + Future.microtask(() => Provider.of(context, listen: false).navigationIndex = 0); } @override @@ -102,7 +102,7 @@ class _State extends State { ); Future _enterAddArtist() async { - final _model = Provider.of(context, listen: false); + final _model = Provider.of(context, listen: false); _model.addSearchQuery = ''; final dynamic result = await Navigator.of(context).pushNamed(LidarrAddSearch.ROUTE_NAME); if(result != null) switch(result[0]) { @@ -124,7 +124,7 @@ class _State extends State { _refreshAllPages(); break; } - default: Logger.warning('Lidarr', '_enterAddArtist', 'Unknown Case: ${result[0]}'); + default: LunaLogger.warning('Lidarr', '_enterAddArtist', 'Unknown Case: ${result[0]}'); } } @@ -151,11 +151,11 @@ class _State extends State { .catchError((_) => LSSnackBar(context: context, title: 'Failed to Search', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure)); break; } - default: Logger.warning('Lidarr', '_handlePopup', 'Unknown Case: ${values[1]}'); + default: LunaLogger.warning('Lidarr', '_handlePopup', 'Unknown Case: ${values[1]}'); } } - void _onPageChanged(int index) => Provider.of(context, listen: false).navigationIndex = index; + void _onPageChanged(int index) => Provider.of(context, listen: false).navigationIndex = index; void _refreshProfile() { _api = LidarrAPI.from(Database.currentProfileObject); diff --git a/lib/modules/lidarr/routes/search_results.dart b/lib/modules/lidarr/routes/search_results.dart index e1e108b9..c6a85d2e 100644 --- a/lib/modules/lidarr/routes/search_results.dart +++ b/lib/modules/lidarr/routes/search_results.dart @@ -43,7 +43,7 @@ class _State extends State { final _api = LidarrAPI.from(Database.currentProfileObject); setState(() => { _future = _api.getReleases(_arguments.albumID) }); //Clear the search filter using a microtask - Future.microtask(() => Provider.of(context, listen: false)?.searchReleasesFilter = ''); + Future.microtask(() => Provider.of(context, listen: false)?.searchReleasesFilter = ''); } @override @@ -55,7 +55,11 @@ class _State extends State { Widget get _appBar => _arguments == null ? null - : LSAppBar(title: _arguments.title); + : LunaAppBar( + context: context, + popUntil: '/lidarr', + title: _arguments.title, + ); Widget get _body => _arguments == null ? null @@ -97,7 +101,7 @@ class _State extends State { buttonText: 'Refresh', onTapHandler: () => _refresh(), ) - : Consumer( + : Consumer( builder: (context, model, widget) { List _filtered = _sort(model, _filter(model.searchReleasesFilter)); _filtered = model.hideRejectedReleases ? _hide(_filtered) : _filtered; @@ -129,8 +133,8 @@ class _State extends State { : entry.title.toLowerCase().contains(filter.toLowerCase()) ).toList(); - List _sort(LidarrModel model, List data) { - if(data != null && data.length != 0) return model.sortReleasesType.sort(data, model.sortReleasesAscending); + List _sort(LidarrState state, List data) { + if(data != null && data.length != 0) return state.sortReleasesType.sort(data, state.sortReleasesAscending); return data; } diff --git a/lib/modules/lidarr/widgets/add_search_bar.dart b/lib/modules/lidarr/widgets/add_search_bar.dart index 360ea61e..2546fbf2 100644 --- a/lib/modules/lidarr/widgets/add_search_bar.dart +++ b/lib/modules/lidarr/widgets/add_search_bar.dart @@ -19,13 +19,13 @@ class _State extends State { @override void initState() { super.initState(); - final model = Provider.of(context, listen: false); + final model = Provider.of(context, listen: false); _controller.text = model.addSearchQuery; } @override Widget build(BuildContext context) => Expanded( - child: Consumer( + child: Consumer( builder: (context, model, widget) => LSTextInputBar( controller: _controller, autofocus: true, @@ -36,8 +36,8 @@ class _State extends State { ), ); - void _onChange(LidarrModel model, String text, bool updateController) { - model.addSearchQuery = text; + void _onChange(LidarrState state, String text, bool updateController) { + state.addSearchQuery = text; if(updateController) _controller.text = text; } diff --git a/lib/modules/lidarr/widgets/add_search_result_tile.dart b/lib/modules/lidarr/widgets/add_search_result_tile.dart index 301b05dc..117b983f 100644 --- a/lib/modules/lidarr/widgets/add_search_result_tile.dart +++ b/lib/modules/lidarr/widgets/add_search_result_tile.dart @@ -42,7 +42,7 @@ class LidarrAddSearchResultTile extends StatelessWidget { ); if(result != null) switch(result[0]) { case 'artist_added': Navigator.of(context).pop(result); break; - default: Logger.warning('LidarrAddSearchResultTile', '_enterDetails', 'Unknown Case: ${result[0]}'); + default: LunaLogger.warning('LidarrAddSearchResultTile', '_enterDetails', 'Unknown Case: ${result[0]}'); } } } diff --git a/lib/modules/lidarr/widgets/artist_navigation_bar.dart b/lib/modules/lidarr/widgets/artist_navigation_bar.dart index bdbb1d6c..8aaca71c 100644 --- a/lib/modules/lidarr/widgets/artist_navigation_bar.dart +++ b/lib/modules/lidarr/widgets/artist_navigation_bar.dart @@ -26,8 +26,8 @@ class _State extends State { ]; @override - Widget build(BuildContext context) => Selector( - selector: (_, model) => model.artistNavigationIndex, + Widget build(BuildContext context) => Selector( + selector: (_, state) => state.artistNavigationIndex, builder: (context, index, _) => LSBottomNavigationBar( index: index, icons: _navbarIcons, @@ -41,6 +41,6 @@ class _State extends State { index, duration: Duration(milliseconds: Constants.UI_NAVIGATION_SPEED), curve: Curves.easeOutSine, - ).then((_) => Provider.of(context, listen: false).artistNavigationIndex = index); + ).then((_) => Provider.of(context, listen: false).artistNavigationIndex = index); } } diff --git a/lib/modules/lidarr/widgets/catalogue_hide_button.dart b/lib/modules/lidarr/widgets/catalogue_hide_button.dart index 9cdebe2a..204dfe15 100644 --- a/lib/modules/lidarr/widgets/catalogue_hide_button.dart +++ b/lib/modules/lidarr/widgets/catalogue_hide_button.dart @@ -17,7 +17,7 @@ class LidarrCatalogueHideButton extends StatefulWidget { class _State extends State { @override Widget build(BuildContext context) => LSCard( - child: Consumer( + child: Consumer( builder: (context, model, widget) => InkWell( child: LSIconButton( icon: model.hideUnmonitoredArtists diff --git a/lib/modules/lidarr/widgets/catalogue_search_bar.dart b/lib/modules/lidarr/widgets/catalogue_search_bar.dart index d3d3c438..4d7ed0ae 100644 --- a/lib/modules/lidarr/widgets/catalogue_search_bar.dart +++ b/lib/modules/lidarr/widgets/catalogue_search_bar.dart @@ -12,18 +12,18 @@ class _State extends State { @override Widget build(BuildContext context) => Expanded( - child: Consumer( - builder: (context, model, widget) => LSTextInputBar( + child: Consumer( + builder: (context, state, widget) => LSTextInputBar( controller: _textController, labelText: 'Search Artists...', - onChanged: (text, update) => _onChanged(model, text, update), + onChanged: (text, update) => _onChanged(state, text, update), margin: EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 12.0), ), ), ); - void _onChanged(LidarrModel model, String text, bool update) { - model.searchCatalogueFilter = text; + void _onChanged(LidarrState state, String text, bool update) { + state.searchCatalogueFilter = text; if(update) _textController.text = ''; } } diff --git a/lib/modules/lidarr/widgets/catalogue_sorting_button.dart b/lib/modules/lidarr/widgets/catalogue_sorting_button.dart index a52b2f59..568762b7 100644 --- a/lib/modules/lidarr/widgets/catalogue_sorting_button.dart +++ b/lib/modules/lidarr/widgets/catalogue_sorting_button.dart @@ -17,7 +17,7 @@ class LidarrCatalogueSortButton extends StatefulWidget { class _State extends State { @override Widget build(BuildContext context) => LSCard( - child: Consumer( + child: Consumer( builder: (context, model, widget) => PopupMenuButton( shape: LunaSeaDatabaseValue.THEME_AMOLED.data && LunaSeaDatabaseValue.THEME_AMOLED_BORDER.data ? LSRoundedShapeWithBorder() @@ -50,7 +50,7 @@ class _State extends State { ? Icons.arrow_upward : Icons.arrow_downward, size: Constants.UI_FONT_SIZE_SUBTITLE+2.0, - color: LSColors.accent, + color: LunaColours.accent, ), ], ), diff --git a/lib/modules/lidarr/widgets/catalogue_tile.dart b/lib/modules/lidarr/widgets/catalogue_tile.dart index dea2c403..7e1db4dc 100644 --- a/lib/modules/lidarr/widgets/catalogue_tile.dart +++ b/lib/modules/lidarr/widgets/catalogue_tile.dart @@ -26,8 +26,8 @@ class _State extends State { text: widget.data.title, darken: !widget.data.monitored, ), - subtitle: Selector( - selector: (_, model) => model.sortCatalogueType, + subtitle: Selector( + selector: (_, state) => state.sortCatalogueType, builder: (context, type, _) => LSSubtitle( text: widget.data.subtitle(type), darken: !widget.data.monitored, @@ -47,7 +47,6 @@ class _State extends State { decoration: LSCardBackground( uri: widget.data.bannerURI(), headers: Database.currentProfileObject.getLidarr()['headers'], - darken: !widget.data.monitored, ), onTap: () async => _enterArtist(), onLongPress: () async => _handlePopup(), @@ -95,7 +94,7 @@ class _State extends State { widget.refresh(); break; } - default: Logger.warning('LidarrCatalogueTile', '_enterArtist', 'Unknown Case: ${result[0]}'); + default: LunaLogger.warning('LidarrCatalogueTile', '_enterArtist', 'Unknown Case: ${result[0]}'); } } @@ -105,7 +104,7 @@ class _State extends State { case 'refresh_artist': _refreshArtist(); break; case 'edit_artist': _enterEditArtist(); break; case 'remove_artist': _removeArtist(); break; - default: Logger.warning('LidarrCatalogueTile', '_handlePopup', 'Invalid method passed through popup. (${values[1]})'); + default: LunaLogger.warning('LidarrCatalogueTile', '_handlePopup', 'Invalid method passed through popup. (${values[1]})'); } } @@ -134,7 +133,7 @@ class _State extends State { List values = await LidarrDialogs.deleteArtist(context); if(values[0]) { if(values[1]) { - values = await GlobalDialogs.deleteCatalogueWithFiles(context, widget.data.title); + values = await LunaDialogs.deleteCatalogueWithFiles(context, widget.data.title); if(values[0]) { await _api.removeArtist(widget.data.artistID, deleteFiles: true) .then((_) { diff --git a/lib/modules/lidarr/widgets/details_album_list.dart b/lib/modules/lidarr/widgets/details_album_list.dart index bdc7b8a5..98514046 100644 --- a/lib/modules/lidarr/widgets/details_album_list.dart +++ b/lib/modules/lidarr/widgets/details_album_list.dart @@ -65,7 +65,7 @@ class _State extends State with AutomaticKeepAliveClient ), ); - Widget get _list => Consumer( + Widget get _list => Consumer( builder: (context, model, widget) { List _filtered = model.hideUnmonitoredAlbums ? _hide(_results) : _results; return LSListViewBuilder( diff --git a/lib/modules/lidarr/widgets/details_album_tile.dart b/lib/modules/lidarr/widgets/details_album_tile.dart index 78d582bf..b6243289 100644 --- a/lib/modules/lidarr/widgets/details_album_tile.dart +++ b/lib/modules/lidarr/widgets/details_album_tile.dart @@ -50,7 +50,7 @@ class _State extends State { TextSpan( text: '\n${widget.data.releaseDateString}', style: TextStyle( - color: widget.data.monitored ? LSColors.accent : LSColors.accent.withOpacity(0.30), + color: widget.data.monitored ? LunaColours.accent : LunaColours.accent.withOpacity(0.30), fontWeight: FontWeight.bold, ), ), diff --git a/lib/modules/lidarr/widgets/details_edit_button.dart b/lib/modules/lidarr/widgets/details_edit_button.dart index 4e82a57e..f8bfd37f 100644 --- a/lib/modules/lidarr/widgets/details_edit_button.dart +++ b/lib/modules/lidarr/widgets/details_edit_button.dart @@ -17,7 +17,7 @@ class LidarrDetailsEditButton extends StatefulWidget { class _State extends State { @override - Widget build(BuildContext context) => Consumer( + Widget build(BuildContext context) => Consumer( builder: (context, model, widget) => LSIconButton( icon: Icons.edit, onPressed: () async => _handlePopup(context), @@ -30,7 +30,7 @@ class _State extends State { case 'refresh_artist': _refreshArtist(context); break; case 'edit_artist': _enterEditArtist(context); break; case 'remove_artist': _removeArtist(context); break; - default: Logger.warning('LidarrDetailsEditButton', '_handlePopup', 'Invalid method passed through popup. (${values[1]})'); + default: LunaLogger.warning('LidarrDetailsEditButton', '_handlePopup', 'Invalid method passed through popup. (${values[1]})'); } } @@ -59,7 +59,7 @@ class _State extends State { List values = await LidarrDialogs.deleteArtist(context); if(values[0]) { if(values[1]) { - values = await GlobalDialogs.deleteCatalogueWithFiles(context, widget.data.title); + values = await LunaDialogs.deleteCatalogueWithFiles(context, widget.data.title); if(values[0]) { await _api.removeArtist(widget.data.artistID, deleteFiles: true) .then((_) => widget.remove(true)) diff --git a/lib/modules/lidarr/widgets/details_hide_button.dart b/lib/modules/lidarr/widgets/details_hide_button.dart index d0dbdd60..95af16e8 100644 --- a/lib/modules/lidarr/widgets/details_hide_button.dart +++ b/lib/modules/lidarr/widgets/details_hide_button.dart @@ -4,7 +4,7 @@ import 'package:lunasea/modules/lidarr.dart'; class LidarrDetailsHideButton extends StatelessWidget { @override - Widget build(BuildContext context) => Consumer( + Widget build(BuildContext context) => Consumer( builder: (context, model, widget) => LSIconButton( icon: model.hideUnmonitoredAlbums ? Icons.visibility_off : Icons.visibility, onPressed: () => model.hideUnmonitoredAlbums = !model.hideUnmonitoredAlbums, diff --git a/lib/modules/lidarr/widgets/details_overview.dart b/lib/modules/lidarr/widgets/details_overview.dart index a5ad7d6b..94132359 100644 --- a/lib/modules/lidarr/widgets/details_overview.dart +++ b/lib/modules/lidarr/widgets/details_overview.dart @@ -36,7 +36,7 @@ class _State extends State with AutomaticKeepAliveClientM LSCardTile( title: LSTitle(text: 'Artist Path', centerText: true), subtitle: LSSubtitle(text: widget?.data?.path ?? 'Unknown', centerText: true), - onTap: () => GlobalDialogs.textPreview(context, 'Artist Path', widget?.data?.path ?? 'Unknown'), + onTap: () => LunaDialogs.textPreview(context, 'Artist Path', widget?.data?.path ?? 'Unknown'), ), LSContainerRow( children: [ diff --git a/lib/modules/lidarr/widgets/navigation_bar.dart b/lib/modules/lidarr/widgets/navigation_bar.dart index 39baad80..e5546521 100644 --- a/lib/modules/lidarr/widgets/navigation_bar.dart +++ b/lib/modules/lidarr/widgets/navigation_bar.dart @@ -31,13 +31,13 @@ class _State extends State { void initState() { super.initState(); SchedulerBinding.instance.scheduleFrameCallback((_) { - Provider.of(context, listen: false).navigationIndex = LidarrDatabaseValue.NAVIGATION_INDEX.data; + Provider.of(context, listen: false).navigationIndex = LidarrDatabaseValue.NAVIGATION_INDEX.data; }); } @override - Widget build(BuildContext context) => Selector( - selector: (_, model) => model.navigationIndex, + Widget build(BuildContext context) => Selector( + selector: (_, state) => state.navigationIndex, builder: (context, index, _) => LSBottomNavigationBar( index: index, icons: LidarrNavigationBar.icons, @@ -52,6 +52,6 @@ class _State extends State { duration: Duration(milliseconds: Constants.UI_NAVIGATION_SPEED), curve: Curves.easeOutSine, ); - Provider.of(context, listen: false).navigationIndex = index; + Provider.of(context, listen: false).navigationIndex = index; } } \ No newline at end of file diff --git a/lib/modules/lidarr/widgets/releases_hide_button.dart b/lib/modules/lidarr/widgets/releases_hide_button.dart index 202d8746..180359ec 100644 --- a/lib/modules/lidarr/widgets/releases_hide_button.dart +++ b/lib/modules/lidarr/widgets/releases_hide_button.dart @@ -17,7 +17,7 @@ class LidarrReleasesHideButton extends StatefulWidget { class _State extends State { @override Widget build(BuildContext context) => LSCard( - child: Consumer( + child: Consumer( builder: (context, model, widget) => InkWell( child: LSIconButton( icon: model.hideRejectedReleases diff --git a/lib/modules/lidarr/widgets/releases_search_bar.dart b/lib/modules/lidarr/widgets/releases_search_bar.dart index f154b6dd..c7796950 100644 --- a/lib/modules/lidarr/widgets/releases_search_bar.dart +++ b/lib/modules/lidarr/widgets/releases_search_bar.dart @@ -24,18 +24,18 @@ class _State extends State { @override Widget build(BuildContext context) => Expanded( - child: Consumer( - builder: (context, model, widget) => LSTextInputBar( + child: Consumer( + builder: (context, state, widget) => LSTextInputBar( controller: _textController, labelText: 'Search Releases...', - onChanged: (text, update) => _onChanged(model, text, update), + onChanged: (text, update) => _onChanged(state, text, update), margin: EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 12.0), ), ), ); - void _onChanged(LidarrModel model, String text, bool update) { - model.searchReleasesFilter = text; + void _onChanged(LidarrState state, String text, bool update) { + state.searchReleasesFilter = text; if(update) _textController.text = ''; } } diff --git a/lib/modules/lidarr/widgets/releases_sorting_button.dart b/lib/modules/lidarr/widgets/releases_sorting_button.dart index 6405310b..56946845 100644 --- a/lib/modules/lidarr/widgets/releases_sorting_button.dart +++ b/lib/modules/lidarr/widgets/releases_sorting_button.dart @@ -17,7 +17,7 @@ class LidarrReleasesSortButton extends StatefulWidget { class _State extends State { @override Widget build(BuildContext context) => LSCard( - child: Consumer( + child: Consumer( builder: (context, model, widget) => PopupMenuButton( shape: LunaSeaDatabaseValue.THEME_AMOLED.data && LunaSeaDatabaseValue.THEME_AMOLED_BORDER.data ? LSRoundedShapeWithBorder() @@ -50,7 +50,7 @@ class _State extends State { ? Icons.arrow_upward : Icons.arrow_downward, size: Constants.UI_FONT_SIZE_SUBTITLE+2.0, - color: LSColors.accent, + color: LunaColours.accent, ), ], ), diff --git a/lib/modules/lidarr/widgets/search_result_tile.dart b/lib/modules/lidarr/widgets/search_result_tile.dart index c8e8326b..c4c04083 100644 --- a/lib/modules/lidarr/widgets/search_result_tile.dart +++ b/lib/modules/lidarr/widgets/search_result_tile.dart @@ -38,8 +38,8 @@ class LidarrSearchResultTile extends StatelessWidget { LSTextHighlighted( text: data.protocol.lsLanguage_Capitalize(), bgColor: data.isTorrent - ? LSColors.purple - : LSColors.blue, + ? LunaColours.purple + : LunaColours.blue, ), ], ), @@ -56,7 +56,7 @@ class LidarrSearchResultTile extends StatelessWidget { if(data.isTorrent) TextSpan( text: '${data.seeders} Seeders\t•\t${data.leechers} Leechers\n', style: TextStyle( - color: LSColors.purple, + color: LunaColours.purple, fontWeight: FontWeight.bold, ), ), @@ -85,7 +85,7 @@ class LidarrSearchResultTile extends StatelessWidget { if(!data.approved) Expanded( child: LSButtonSlim( text: 'Rejected', - backgroundColor: LSColors.red, + backgroundColor: LunaColours.red, onTap: () => _showWarnings(context), margin: EdgeInsets.only(left: 6.0), ), @@ -118,8 +118,8 @@ class LidarrSearchResultTile extends StatelessWidget { TextSpan( style: TextStyle( color: data.isTorrent - ? LSColors.purple - : LSColors.blue, + ? LunaColours.purple + : LunaColours.blue, fontWeight: FontWeight.bold, ), text: data.protocol.lsLanguage_Capitalize(), @@ -127,7 +127,7 @@ class LidarrSearchResultTile extends StatelessWidget { if(data.isTorrent) TextSpan( text: ' (${data.seeders}/${data.leechers})', style: TextStyle( - color: LSColors.purple, + color: LunaColours.purple, fontWeight: FontWeight.bold, ), ), @@ -144,7 +144,7 @@ class LidarrSearchResultTile extends StatelessWidget { : Icons.report, color: data.approved ? Colors.white - : LSColors.red, + : LunaColours.red, onPressed: () async => data.approved ? _startDownload(context) : _showWarnings(context), @@ -182,6 +182,6 @@ class LidarrSearchResultTile extends StatelessWidget { for(var i=0; i true; - }; - } return NZBGetAPI._internal( profile.getNZBGet(), _client, ); } - void logWarning(String methodName, String text) => Logger.warning('NZBGetAPI', methodName, 'NZBGet: $text'); + void logWarning(String methodName, String text) => LunaLogger.warning('NZBGetAPI', methodName, 'NZBGet: $text'); void logError(String methodName, String text, Object error, StackTrace trace, { bool uploadToSentry = true, - }) => Logger.error( + }) => LunaLogger.error( 'NZBGetAPI', methodName, 'NZBGet: $text', diff --git a/lib/modules/nzbget/core/api/data/history.dart b/lib/modules/nzbget/core/api/data/history.dart index a3643319..18aa5c21 100644 --- a/lib/modules/nzbget/core/api/data/history.dart +++ b/lib/modules/nzbget/core/api/data/history.dart @@ -66,11 +66,11 @@ class NZBGetHistoryData { Color get statusColor { switch(status.substring(0, 7)) { - case 'SUCCESS': return LSColors.accent; - case 'WARNING': return LSColors.orange; - case 'DELETED': return LSColors.purple; - case 'FAILURE': return LSColors.red; - default: return LSColors.blueGrey; + case 'SUCCESS': return LunaColours.accent; + case 'WARNING': return LunaColours.orange; + case 'DELETED': return LunaColours.purple; + case 'FAILURE': return LunaColours.red; + default: return LunaColours.blueGrey; } } diff --git a/lib/modules/nzbget/core/constants.dart b/lib/modules/nzbget/core/constants.dart index df4a4cc1..67c77913 100644 --- a/lib/modules/nzbget/core/constants.dart +++ b/lib/modules/nzbget/core/constants.dart @@ -6,7 +6,7 @@ class NZBGetConstants { static const MODULE_KEY = 'nzbget'; - static const ModuleMap MODULE_MAP = ModuleMap( + static const LunaModuleMap MODULE_MAP = LunaModuleMap( name: 'NZBGet', description: 'Manage Usenet Downloads', settingsDescription: 'Configure NZBGet', diff --git a/lib/modules/nzbget/core/dialogs.dart b/lib/modules/nzbget/core/dialogs.dart index a50ae727..dbc8efea 100644 --- a/lib/modules/nzbget/core/dialogs.dart +++ b/lib/modules/nzbget/core/dialogs.dart @@ -29,7 +29,7 @@ class NZBGetDialogs { (index) => LSDialog.tile( text: _options[index][0], icon: _options[index][1], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, _options[index][2]), ), ), @@ -66,7 +66,7 @@ class NZBGetDialogs { (index) => LSDialog.tile( text: _options[index][0], icon: _options[index][1], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, _options[index][2]), ), ), @@ -89,7 +89,7 @@ class NZBGetDialogs { buttons: [ LSDialog.button( text: 'Delete', - textColor: LSColors.red, + textColor: LunaColours.red, onPressed: () => _setValues(true), ), ], @@ -158,7 +158,7 @@ class NZBGetDialogs { (index) => LSDialog.tile( text: NZBGetPriority.values[index].name, icon: Icons.low_priority, - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, NZBGetPriority.values[index]), ), ), @@ -185,7 +185,7 @@ class NZBGetDialogs { (index) => LSDialog.tile( text: categories[index].name, icon: Icons.category, - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, categories[index]), ), ), @@ -254,7 +254,7 @@ class NZBGetDialogs { ), LSDialog.button( text: 'Delete', - textColor: LSColors.red, + textColor: LunaColours.red, onPressed: () => _setValues(true, false), ), ], @@ -284,7 +284,7 @@ class NZBGetDialogs { (index) => LSDialog.tile( text: NZBGetSort.values[index].name, icon: NZBGetSort.values[index].icon, - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, NZBGetSort.values[index]), ), ), @@ -315,7 +315,7 @@ class NZBGetDialogs { (index) => LSDialog.tile( text: _options[index][0], icon: _options[index][1], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, _options[index][2]), ), ), @@ -392,7 +392,7 @@ class NZBGetDialogs { (index) => LSDialog.tile( text: _options[index][0], icon: _options[index][1], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, _options[index][2]), ), ), @@ -469,7 +469,7 @@ class NZBGetDialogs { (index) => LSDialog.tile( text: _options[index][0], icon: _options[index][1], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, _options[index][2]), ), ), @@ -505,7 +505,7 @@ class NZBGetDialogs { (index) => LSDialog.tile( text: _options[index][0], icon: _options[index][1], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, _options[index][2]), ), ), @@ -584,7 +584,7 @@ class NZBGetDialogs { (index) => LSDialog.tile( text: NZBGetNavigationBar.titles[index], icon: NZBGetNavigationBar.icons[index], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, index), ), ), diff --git a/lib/modules/nzbget/core/state.dart b/lib/modules/nzbget/core/state.dart new file mode 100644 index 00000000..ed5ebd8f --- /dev/null +++ b/lib/modules/nzbget/core/state.dart @@ -0,0 +1,2 @@ +export 'state/global.dart'; +export 'state/local.dart'; diff --git a/lib/modules/nzbget/core/state_global.dart b/lib/modules/nzbget/core/state/global.dart similarity index 93% rename from lib/modules/nzbget/core/state_global.dart rename to lib/modules/nzbget/core/state/global.dart index 828028ea..41f483ef 100644 --- a/lib/modules/nzbget/core/state_global.dart +++ b/lib/modules/nzbget/core/state/global.dart @@ -1,6 +1,13 @@ -import 'package:flutter/foundation.dart'; +import 'package:lunasea/core.dart'; -class NZBGetModel extends ChangeNotifier { +class NZBGetState extends LunaGlobalState { + NZBGetState() { + reset(); + } + + @override + void reset() {} + bool _error = false; bool get error => _error; set error(bool error) { diff --git a/lib/modules/nzbget/core/state/local.dart b/lib/modules/nzbget/core/state/local.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/modules/nzbget/routes/history.dart b/lib/modules/nzbget/routes/history.dart index af968f70..09d74d5b 100644 --- a/lib/modules/nzbget/routes/history.dart +++ b/lib/modules/nzbget/routes/history.dart @@ -35,7 +35,7 @@ class _State extends State with AutomaticKeepAliveClientMixin { _results = []; final _api = NZBGetAPI.from(Database.currentProfileObject); if(mounted) setState(() { _future = _api.getHistory(); }); - Future.microtask(() => Provider.of(context, listen: false)?.historySearchFilter = ''); + Future.microtask(() => Provider.of(context, listen: false)?.historySearchFilter = ''); } @override @@ -84,7 +84,7 @@ class _State extends State with AutomaticKeepAliveClientMixin { buttonText: 'Refresh', onTapHandler: () => _refresh(), ) - : Selector>( + : Selector>( selector: (_, model) => Tuple2(model.historySearchFilter, model.historyHideFailed), builder: (context, data, _) { List _filtered = _filter(data.item1); diff --git a/lib/modules/nzbget/routes/nzbget.dart b/lib/modules/nzbget/routes/nzbget.dart index 7aeaa286..909f71e5 100644 --- a/lib/modules/nzbget/routes/nzbget.dart +++ b/lib/modules/nzbget/routes/nzbget.dart @@ -25,7 +25,7 @@ class _State extends State { @override void initState() { super.initState(); - Future.microtask(() => Provider.of(context, listen: false).navigationIndex = 0); + Future.microtask(() => Provider.of(context, listen: false).navigationIndex = 0); } @override @@ -80,7 +80,7 @@ class _State extends State { }), actions: _api.enabled ? [ - Selector( + Selector( selector: (_, model) => model.error, builder: (context, error, widget) => error ? Container() @@ -101,7 +101,7 @@ class _State extends State { case 'add_nzb': _addNZB(); break; case 'sort': _sort(); break; case 'server_details': _serverDetails(); break; - default: Logger.warning('NZBGet', '_handlePopup', 'Unknown Case: ${values[1]}'); + default: LunaLogger.warning('NZBGet', '_handlePopup', 'Unknown Case: ${values[1]}'); } } @@ -110,7 +110,7 @@ class _State extends State { if(values[0]) switch(values[1]) { case 'link': _addByURL(); break; case 'file': _addByFile(); break; - default: Logger.warning('NZBGet', '_addNZB', 'Unknown Case: ${values[1]}'); + default: LunaLogger.warning('NZBGet', '_addNZB', 'Unknown Case: ${values[1]}'); } } @@ -193,7 +193,7 @@ class _State extends State { Future _serverDetails() async => Navigator.of(context).pushNamed(NZBGetStatistics.ROUTE_NAME); - void _onPageChanged(int index) => Provider.of(context, listen: false).navigationIndex = index; + void _onPageChanged(int index) => Provider.of(context, listen: false).navigationIndex = index; void _refreshProfile() { _api = NZBGetAPI.from(Database.currentProfileObject); diff --git a/lib/modules/nzbget/routes/queue.dart b/lib/modules/nzbget/routes/queue.dart index 1dacd706..62d96f60 100644 --- a/lib/modules/nzbget/routes/queue.dart +++ b/lib/modules/nzbget/routes/queue.dart @@ -81,7 +81,7 @@ class _State extends State with TickerProviderStateMixin, Automatic } Future _fetchQueue(NZBGetAPI api) async { - final _model = Provider.of(context, listen: false); + final _model = Provider.of(context, listen: false); return await api.getQueue(_model.speed, 100) .then((data) => _queue = data) .catchError((error) => Future.error(error)); @@ -91,7 +91,7 @@ class _State extends State with TickerProviderStateMixin, Automatic return await api.getStatus() .then((data) { if(mounted) { - final _model = Provider.of(context, listen: false); + final _model = Provider.of(context, listen: false); _model.paused = data.paused; _model.speed = data.speed; _model.currentSpeed = data.currentSpeed; @@ -104,7 +104,7 @@ class _State extends State with TickerProviderStateMixin, Automatic } void _setError(bool error) { - final _model = Provider.of(context, listen: false); + final _model = Provider.of(context, listen: false); _model.error = error; } diff --git a/lib/modules/nzbget/routes/statistics.dart b/lib/modules/nzbget/routes/statistics.dart index 5fe305ea..d1485782 100644 --- a/lib/modules/nzbget/routes/statistics.dart +++ b/lib/modules/nzbget/routes/statistics.dart @@ -56,7 +56,11 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Server Statistics'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/nzbget', + title: 'Server Statistics', + ); Widget get _body => LSRefreshIndicator( refreshKey: _refreshKey, diff --git a/lib/modules/nzbget/widgets/app_bar_stats.dart b/lib/modules/nzbget/widgets/app_bar_stats.dart index 477a20c6..a5940e9b 100644 --- a/lib/modules/nzbget/widgets/app_bar_stats.dart +++ b/lib/modules/nzbget/widgets/app_bar_stats.dart @@ -5,7 +5,7 @@ import 'package:lunasea/modules/nzbget.dart'; class NZBGetAppBarStats extends StatelessWidget { @override - Widget build(BuildContext context) => Selector>( + Widget build(BuildContext context) => Selector>( selector: (_, model) => Tuple5( model.paused, //item1 model.currentSpeed, //item2 @@ -28,7 +28,7 @@ class NZBGetAppBarStats extends StatelessWidget { style: TextStyle( fontWeight: FontWeight.bold, fontSize: Constants.UI_FONT_SIZE_HEADER, - color: LSColors.accent, + color: LunaColours.accent, ), ), TextSpan(text: '\n'), diff --git a/lib/modules/nzbget/widgets/history_hide_button.dart b/lib/modules/nzbget/widgets/history_hide_button.dart index 6f34ca22..20cadbaa 100644 --- a/lib/modules/nzbget/widgets/history_hide_button.dart +++ b/lib/modules/nzbget/widgets/history_hide_button.dart @@ -10,7 +10,7 @@ class NZBGetHistoryHideButton extends StatefulWidget { class _State extends State { @override Widget build(BuildContext context) => LSCard( - child: Consumer( + child: Consumer( builder: (context, model, widget) => InkWell( child: LSIconButton( icon: model.historyHideFailed diff --git a/lib/modules/nzbget/widgets/history_search_bar.dart b/lib/modules/nzbget/widgets/history_search_bar.dart index de1884b1..16034e11 100644 --- a/lib/modules/nzbget/widgets/history_search_bar.dart +++ b/lib/modules/nzbget/widgets/history_search_bar.dart @@ -12,7 +12,7 @@ class _State extends State { @override Widget build(BuildContext context) => Expanded( - child: Consumer( + child: Consumer( builder: (context, model, widget) => LSTextInputBar( controller: _textController, labelText: 'Search History...', @@ -22,7 +22,7 @@ class _State extends State { ), ); - void _onChanged(NZBGetModel model, String text, bool update) { + void _onChanged(NZBGetState model, String text, bool update) { model.historySearchFilter = text; if(update) _textController.text = ''; } diff --git a/lib/modules/nzbget/widgets/history_tile.dart b/lib/modules/nzbget/widgets/history_tile.dart index 04f018fa..8907d771 100644 --- a/lib/modules/nzbget/widgets/history_tile.dart +++ b/lib/modules/nzbget/widgets/history_tile.dart @@ -45,7 +45,7 @@ class NZBGetHistoryTile extends StatelessWidget { ), LSTextHighlighted( text: data.healthString, - bgColor: LSColors.blueGrey, + bgColor: LunaColours.blueGrey, ) ], ), @@ -89,7 +89,7 @@ class NZBGetHistoryTile extends StatelessWidget { Expanded( child: LSButtonSlim( text: 'Delete', - backgroundColor: LSColors.red, + backgroundColor: LunaColours.red, onTap: () async => _deleteButton(context), margin: EdgeInsets.zero, ), diff --git a/lib/modules/nzbget/widgets/log_tile.dart b/lib/modules/nzbget/widgets/log_tile.dart index 9d6497f4..7d238ac5 100644 --- a/lib/modules/nzbget/widgets/log_tile.dart +++ b/lib/modules/nzbget/widgets/log_tile.dart @@ -14,6 +14,6 @@ class NZBGetLogTile extends StatelessWidget { title: LSTitle(text: data.text), subtitle: LSSubtitle(text: data.timestamp), trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: () async => GlobalDialogs.textPreview(context, 'Log Entry', data.text), + onTap: () async => LunaDialogs.textPreview(context, 'Log Entry', data.text), ); } diff --git a/lib/modules/nzbget/widgets/navigation_bar.dart b/lib/modules/nzbget/widgets/navigation_bar.dart index 8703baf6..e938969d 100644 --- a/lib/modules/nzbget/widgets/navigation_bar.dart +++ b/lib/modules/nzbget/widgets/navigation_bar.dart @@ -29,12 +29,12 @@ class _State extends State { void initState() { super.initState(); SchedulerBinding.instance.scheduleFrameCallback((_) { - Provider.of(context, listen: false).navigationIndex = NZBGetDatabaseValue.NAVIGATION_INDEX.data; + Provider.of(context, listen: false).navigationIndex = NZBGetDatabaseValue.NAVIGATION_INDEX.data; }); } @override - Widget build(BuildContext context) => Selector( + Widget build(BuildContext context) => Selector( selector: (_, model) => model.navigationIndex, builder: (context, index, _) => LSBottomNavigationBar( index: index, @@ -50,6 +50,6 @@ class _State extends State { duration: Duration(milliseconds: Constants.UI_NAVIGATION_SPEED), curve: Curves.easeOutSine, ); - Provider.of(context, listen: false).navigationIndex = index; + Provider.of(context, listen: false).navigationIndex = index; } } diff --git a/lib/modules/nzbget/widgets/queue_fab.dart b/lib/modules/nzbget/widgets/queue_fab.dart index 84233616..b9003da6 100644 --- a/lib/modules/nzbget/widgets/queue_fab.dart +++ b/lib/modules/nzbget/widgets/queue_fab.dart @@ -64,7 +64,7 @@ class _State extends State with TickerProviderStateMixin { } @override - Widget build(BuildContext context) => Selector>( + Widget build(BuildContext context) => Selector>( selector: (_, model) => Tuple2(model.error, model.paused), builder: (context, data, _) { data.item2 @@ -134,7 +134,7 @@ class _State extends State with TickerProviderStateMixin { _iconController.forward(); await api.pauseQueue() .then((_) { - Provider.of(context, listen: false).paused = true; + Provider.of(context, listen: false).paused = true; }) .catchError((_) { _iconController.reverse(); @@ -151,7 +151,7 @@ class _State extends State with TickerProviderStateMixin { _iconController.reverse(); return await api.resumeQueue() .then((_) { - Provider.of(context, listen: false).paused = false; + Provider.of(context, listen: false).paused = false; }) .catchError((_) { _iconController.forward(); diff --git a/lib/modules/nzbget/widgets/queue_tile.dart b/lib/modules/nzbget/widgets/queue_tile.dart index bb72ecb6..c47c04d0 100644 --- a/lib/modules/nzbget/widgets/queue_tile.dart +++ b/lib/modules/nzbget/widgets/queue_tile.dart @@ -37,8 +37,8 @@ class _State extends State { child: LinearPercentIndicator( percent: min(1.0, max(0, widget.data.percentageDone/100)), padding: EdgeInsets.symmetric(horizontal: 2.0), - progressColor: widget.data.paused ? LSColors.accent.withOpacity(0.30) : LSColors.accent, - backgroundColor: widget.data.paused ? LSColors.accent.withOpacity(0.05) : LSColors.accent.withOpacity(0.15), + progressColor: widget.data.paused ? LunaColours.accent.withOpacity(0.30) : LunaColours.accent, + backgroundColor: widget.data.paused ? LunaColours.accent.withOpacity(0.05) : LunaColours.accent.withOpacity(0.15), lineHeight: 4.0, ), padding: EdgeInsets.symmetric(vertical: 6.0), @@ -69,7 +69,7 @@ class _State extends State { case 'password': _helper._password(); break; case 'rename': _helper._rename(); break; case 'delete': _helper._delete(); break; - default: Logger.warning('NZBGetQueueTile', '_handlePopup', 'Unknown Case: ${values[1]}'); + default: LunaLogger.warning('NZBGetQueueTile', '_handlePopup', 'Unknown Case: ${values[1]}'); } } } diff --git a/lib/modules/ombi.dart b/lib/modules/ombi.dart new file mode 100644 index 00000000..3d14222f --- /dev/null +++ b/lib/modules/ombi.dart @@ -0,0 +1,2 @@ +export 'ombi/core.dart'; +export 'ombi/modules.dart'; diff --git a/lib/modules/ombi/core.dart b/lib/modules/ombi/core.dart new file mode 100644 index 00000000..c2a19ad4 --- /dev/null +++ b/lib/modules/ombi/core.dart @@ -0,0 +1,7 @@ +export 'core/constants.dart'; +export 'core/database.dart'; +export 'core/dialogs.dart'; +export 'core/extensions.dart'; +export 'core/router.dart'; +export 'core/state.dart'; +export 'core/types.dart'; diff --git a/lib/modules/ombi/core/constants.dart b/lib/modules/ombi/core/constants.dart new file mode 100644 index 00000000..8f220467 --- /dev/null +++ b/lib/modules/ombi/core/constants.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; + +class OmbiConstants { + OmbiConstants._(); + + static const String MODULE_KEY = 'ombi'; + + static const LunaModuleMap MODULE_MAP = LunaModuleMap( + name: 'Ombi', + description: 'Manage Requests for Media', + settingsDescription: 'Configure Ombi', + icon: CustomIcons.tautulli, + route: '/ombi', + color: Color(0xFFD4782C), + ); + + //ignore: non_constant_identifier_names + static final ShortcutItem MODULE_QUICK_ACTION = ShortcutItem( + type: MODULE_KEY, + localizedTitle: MODULE_MAP.name, + ); +} diff --git a/lib/modules/ombi/core/database.dart b/lib/modules/ombi/core/database.dart new file mode 100644 index 00000000..176c6e6a --- /dev/null +++ b/lib/modules/ombi/core/database.dart @@ -0,0 +1,30 @@ +import 'package:lunasea/core.dart'; + +class OmbiDatabase { + OmbiDatabase._(); + + static void registerAdapters() {} +} + +enum OmbiDatabaseValue { + NAVIGATION_INDEX, +} + +extension OmbiDatabaseValueExtension on OmbiDatabaseValue { + String get key { + switch(this) { + case OmbiDatabaseValue.NAVIGATION_INDEX: return 'OMBI_NAVIGATION_INDEX'; + } + throw Exception('key not found'); + } + + dynamic get data { + final _box = Database.lunaSeaBox; + switch(this) { + case OmbiDatabaseValue.NAVIGATION_INDEX: return _box.get(this.key, defaultValue: 0); + } + throw Exception('data not found'); + } + + void put(dynamic value) => Database.lunaSeaBox.put(this.key, value); +} diff --git a/lib/modules/ombi/core/dialogs.dart b/lib/modules/ombi/core/dialogs.dart new file mode 100644 index 00000000..443dfaf5 --- /dev/null +++ b/lib/modules/ombi/core/dialogs.dart @@ -0,0 +1,3 @@ +class OmbiDialogs { + OmbiDialogs._(); +} diff --git a/lib/modules/ombi/core/extensions.dart b/lib/modules/ombi/core/extensions.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/modules/ombi/core/router.dart b/lib/modules/ombi/core/router.dart new file mode 100644 index 00000000..fa993dac --- /dev/null +++ b/lib/modules/ombi/core/router.dart @@ -0,0 +1,10 @@ +import 'package:fluro_fork/fluro_fork.dart'; +import 'package:lunasea/modules/ombi.dart'; + +class OmbiRouter { + OmbiRouter._(); + + static void initialize(Router router) { + OmbiHomeRouter.defineRoutes(router); + } +} diff --git a/lib/modules/ombi/core/state.dart b/lib/modules/ombi/core/state.dart new file mode 100644 index 00000000..c1f9535e --- /dev/null +++ b/lib/modules/ombi/core/state.dart @@ -0,0 +1,6 @@ +import 'package:lunasea/core.dart'; + +class OmbiState extends LunaGlobalState { + @override + void reset() {} +} diff --git a/lib/modules/ombi/core/types.dart b/lib/modules/ombi/core/types.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/modules/ombi/modules.dart b/lib/modules/ombi/modules.dart new file mode 100644 index 00000000..eb2003f6 --- /dev/null +++ b/lib/modules/ombi/modules.dart @@ -0,0 +1 @@ +export 'modules/ombi.dart'; diff --git a/lib/modules/ombi/modules/ombi.dart b/lib/modules/ombi/modules/ombi.dart new file mode 100644 index 00000000..f953db5b --- /dev/null +++ b/lib/modules/ombi/modules/ombi.dart @@ -0,0 +1 @@ +export 'ombi/route.dart'; diff --git a/lib/modules/ombi/modules/ombi/route.dart b/lib/modules/ombi/modules/ombi/route.dart new file mode 100644 index 00000000..0141a67f --- /dev/null +++ b/lib/modules/ombi/modules/ombi/route.dart @@ -0,0 +1,53 @@ +import 'package:fluro_fork/fluro_fork.dart'; +import 'package:flutter/material.dart' hide Router; +import 'package:lunasea/core.dart'; + +class OmbiHomeRouter { + static const ROUTE_NAME = '/ombi'; + + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) { + router.define( + ROUTE_NAME, + handler: Handler(handlerFunc: (context, params) => _OmbiHomeRoute()), + transitionType: LunaRouter.transitionType, + ); + } + + OmbiHomeRouter._(); +} + +class _OmbiHomeRoute extends StatefulWidget { + @override + State<_OmbiHomeRoute> createState() => _State(); +} + +class _State extends State<_OmbiHomeRoute> { + final GlobalKey _scaffoldKey = GlobalKey(); + + @override + Widget build(BuildContext context) => WillPopScope( + onWillPop: _onWillPop, + child: ValueListenableBuilder( + valueListenable: Database.lunaSeaBox.listenable(keys: [ LunaSeaDatabaseValue.ENABLED_PROFILE.key ]), + builder: (context, box, _) => Scaffold( + key: _scaffoldKey, + drawer: _drawer, + ), + ), + ); + + Future _onWillPop() async { + if(_scaffoldKey.currentState.isDrawerOpen) return true; + _scaffoldKey.currentState.openDrawer(); + return false; + } + + Widget get _drawer => LSDrawer(page: 'ombi'); +} diff --git a/lib/modules/radarr/core/api/api.dart b/lib/modules/radarr/core/api/api.dart index 739e6983..ed7764a8 100644 --- a/lib/modules/radarr/core/api/api.dart +++ b/lib/modules/radarr/core/api/api.dart @@ -1,6 +1,4 @@ import 'dart:convert'; -import 'dart:io'; -import 'package:dio/adapter.dart'; import 'package:dio/dio.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/radarr.dart'; @@ -25,21 +23,16 @@ class RadarrAPI extends API { maxRedirects: 5, ), ); - if(!profile.getRadarr()['strict_tls']) { - (_client.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) { - client.badCertificateCallback = (X509Certificate cert, String host, int port) => true; - }; - } return RadarrAPI._internal( profile.getRadarr(), _client, ); } - void logWarning(String methodName, String text) => Logger.warning('package:lunasea/core/api/radarr/api.dart', methodName, 'Radarr: $text'); + void logWarning(String methodName, String text) => LunaLogger.warning('package:lunasea/core/api/radarr/api.dart', methodName, 'Radarr: $text'); void logError(String methodName, String text, Object error, StackTrace trace, { bool uploadToSentry = true, - }) => Logger.error( + }) => LunaLogger.error( 'package:lunasea/core/api/radarr/api.dart', methodName, 'Radarr: $text', diff --git a/lib/modules/radarr/core/api/data/catalogue.dart b/lib/modules/radarr/core/api/data/catalogue.dart index 366e4e01..e5bb2667 100644 --- a/lib/modules/radarr/core/api/data/catalogue.dart +++ b/lib/modules/radarr/core/api/data/catalogue.dart @@ -158,7 +158,7 @@ class RadarrCatalogueData { DateTime now = DateTime.now(); if (downloaded) { text = sizeOnDisk?.lsBytes_BytesToString(); - color = monitored ? Color(Constants.ACCENT_COLOR) : Color(Constants.ACCENT_COLOR).withOpacity(0.30); + color = monitored ? Color(LunaColours.ACCENT_COLOR) : Color(LunaColours.ACCENT_COLOR).withOpacity(0.30); } else if(isPhysicallyReleased) { text = ''; } else if(isInCinemas) { @@ -173,7 +173,7 @@ class RadarrCatalogueData { } } else if(isTBA) { text = 'TO BE ANNOUNCED'; - color = monitored ? Color(Constants.ACCENT_COLOR) : Color(Constants.ACCENT_COLOR).withOpacity(0.30); + color = monitored ? Color(LunaColours.ACCENT_COLOR) : Color(LunaColours.ACCENT_COLOR).withOpacity(0.30); } return TextSpan( text: text, diff --git a/lib/modules/radarr/core/api/data/history.dart b/lib/modules/radarr/core/api/data/history.dart index ca629d22..f5f02676 100644 --- a/lib/modules/radarr/core/api/data/history.dart +++ b/lib/modules/radarr/core/api/data/history.dart @@ -54,7 +54,7 @@ class RadarrHistoryDataGeneric extends RadarrHistoryData { TextSpan( text: '$eventType', style: TextStyle( - color: LSColors.purple, + color: LunaColours.purple, fontWeight: FontWeight.bold, ), ), @@ -77,7 +77,7 @@ class RadarrHistoryDataFileRenamed extends RadarrHistoryData { TextSpan( text: '${RadarrConstants.EVENT_TYPE_MESSAGES[eventType]}', style: TextStyle( - color: Color(Constants.ACCENT_COLOR), + color: Color(LunaColours.ACCENT_COLOR), fontWeight: FontWeight.bold, ), ), @@ -129,7 +129,7 @@ class RadarrHistoryDataDownloadImported extends RadarrHistoryData { TextSpan( text: '${RadarrConstants.EVENT_TYPE_MESSAGES[eventType]} ($quality)', style: TextStyle( - color: Color(Constants.ACCENT_COLOR), + color: Color(LunaColours.ACCENT_COLOR), fontWeight: FontWeight.bold, ), ), diff --git a/lib/modules/radarr/core/constants.dart b/lib/modules/radarr/core/constants.dart index c629f433..da4c2f24 100644 --- a/lib/modules/radarr/core/constants.dart +++ b/lib/modules/radarr/core/constants.dart @@ -7,7 +7,7 @@ class RadarrConstants { static const String MODULE_KEY = 'radarr'; - static const ModuleMap MODULE_MAP = ModuleMap( + static const LunaModuleMap MODULE_MAP = LunaModuleMap( name: 'Radarr', description: 'Manage Movies', settingsDescription: 'Configure Radarr', diff --git a/lib/modules/radarr/core/dialogs.dart b/lib/modules/radarr/core/dialogs.dart index a69bb4a7..2f35475d 100644 --- a/lib/modules/radarr/core/dialogs.dart +++ b/lib/modules/radarr/core/dialogs.dart @@ -19,7 +19,7 @@ class RadarrDialogs { buttons: [ LSDialog.button( text: 'Delete', - textColor: LSColors.red, + textColor: LunaColours.red, onPressed: () => _setValues(true), ), ], @@ -44,7 +44,7 @@ class RadarrDialogs { buttons: [ LSDialog.button( text: 'Remove', - textColor: LSColors.red, + textColor: LunaColours.red, onPressed: () => _setValues(true), ), ], @@ -102,7 +102,7 @@ class RadarrDialogs { buttons: [ LSDialog.button( text: 'Search', - textColor: LSColors.accent, + textColor: LunaColours.accent, onPressed: () => _setValues(true), ), ], @@ -137,7 +137,7 @@ class RadarrDialogs { (index) => LSDialog.tile( text: _options[index][0], icon: _options[index][1], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, _options[index][2]), ), ), @@ -164,7 +164,7 @@ class RadarrDialogs { (index) => LSDialog.tile( text: availability[index].name, icon: Icons.folder, - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, availability[index]), ), ), @@ -196,7 +196,7 @@ class RadarrDialogs { ], ), icon: Icons.folder, - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, folders[index]), ), ), @@ -223,7 +223,7 @@ class RadarrDialogs { (index) => LSDialog.tile( text: qualities[index].name, icon: Icons.portrait, - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, qualities[index]), ), ), @@ -282,7 +282,7 @@ class RadarrDialogs { (index) => LSDialog.tile( text: _options[index][0], icon: _options[index][1], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, _options[index][2]), ), ), @@ -309,7 +309,7 @@ class RadarrDialogs { (index) => LSDialog.tile( text: RadarrNavigationBar.titles[index], icon: RadarrNavigationBar.icons[index], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, index), ), ), diff --git a/lib/modules/radarr/core/sorting/catalogue.dart b/lib/modules/radarr/core/sorting/catalogue.dart index 3fdd764d..b678bc45 100644 --- a/lib/modules/radarr/core/sorting/catalogue.dart +++ b/lib/modules/radarr/core/sorting/catalogue.dart @@ -46,7 +46,7 @@ extension RadarrCatalogueSortingExtension on RadarrCatalogueSorting { ) => _sorter.byType(data, this, ascending); } -class _Sorter extends Sorter { +class _Sorter extends LunaSorter { @override List byType( List data, diff --git a/lib/modules/radarr/core/sorting/releases.dart b/lib/modules/radarr/core/sorting/releases.dart index 42da19cf..6b9d0c29 100644 --- a/lib/modules/radarr/core/sorting/releases.dart +++ b/lib/modules/radarr/core/sorting/releases.dart @@ -43,7 +43,7 @@ extension RadarrReleasesSortingExtension on RadarrReleasesSorting { ) => _sorter.byType(data, this, ascending); } -class _Sorter extends Sorter { +class _Sorter extends LunaSorter { @override List byType( List data, diff --git a/lib/modules/radarr/core/state.dart b/lib/modules/radarr/core/state.dart index 94f7ec53..ed5ebd8f 100644 --- a/lib/modules/radarr/core/state.dart +++ b/lib/modules/radarr/core/state.dart @@ -1,119 +1,2 @@ -import 'package:flutter/foundation.dart'; -import 'package:lunasea/modules/radarr.dart'; - -class RadarrState extends ChangeNotifier { - ///Catalogue Sticky Header Content - - String _searchCatalogueFilter = ''; - String get searchCatalogueFilter => _searchCatalogueFilter; - set searchCatalogueFilter(String searchCatalogueFilter) { - assert(searchCatalogueFilter != null); - _searchCatalogueFilter = searchCatalogueFilter; - notifyListeners(); - } - - - RadarrCatalogueSorting _sortCatalogueType = RadarrCatalogueSorting.alphabetical; - RadarrCatalogueSorting get sortCatalogueType => _sortCatalogueType; - set sortCatalogueType(RadarrCatalogueSorting sortCatalogueType) { - assert(sortCatalogueType != null); - _sortCatalogueType = sortCatalogueType; - notifyListeners(); - } - - bool _sortCatalogueAscending = true; - bool get sortCatalogueAscending => _sortCatalogueAscending; - set sortCatalogueAscending(bool sortCatalogueAscending) { - assert(sortCatalogueAscending != null); - _sortCatalogueAscending = sortCatalogueAscending; - notifyListeners(); - } - - bool _hideUnmonitoredMovies = false; - bool get hideUnmonitoredMovies => _hideUnmonitoredMovies; - set hideUnmonitoredMovies(bool hideUnmonitoredMovies) { - assert(hideUnmonitoredMovies != null); - _hideUnmonitoredMovies = hideUnmonitoredMovies; - notifyListeners(); - } - - ///Releases Sticky Header Content - - String _searchReleasesFilter = ''; - String get searchReleasesFilter => _searchReleasesFilter; - set searchReleasesFilter(String searchReleasesFilter) { - assert(searchReleasesFilter != null); - _searchReleasesFilter = searchReleasesFilter; - notifyListeners(); - } - - RadarrReleasesSorting _sortReleasesType = RadarrReleasesSorting.weight; - RadarrReleasesSorting get sortReleasesType => _sortReleasesType; - set sortReleasesType(RadarrReleasesSorting sortReleasesType) { - assert(sortReleasesType != null); - _sortReleasesType = sortReleasesType; - notifyListeners(); - } - - bool _sortReleasesAscending = true; - bool get sortReleasesAscending => _sortReleasesAscending; - set sortReleasesAscending(bool sortReleasesAscending) { - assert(sortReleasesAscending != null); - _sortReleasesAscending = sortReleasesAscending; - notifyListeners(); - } - - bool _hideRejectedReleases = false; - bool get hideRejectedReleases => _hideRejectedReleases; - set hideRejectedReleases(bool hideRejectedReleases) { - assert(hideRejectedReleases != null); - _hideRejectedReleases = hideRejectedReleases; - notifyListeners(); - } - - ///Add New Movie Content - - String _addSearchQuery = ''; - String get addSearchQuery => _addSearchQuery; - set addSearchQuery(String addSearchQuery) { - assert(addSearchQuery != null); - _addSearchQuery = addSearchQuery; - notifyListeners(); - } - - ///Delete options - - bool _removeDeleteFiles = false; - bool get removeDeleteFiles => _removeDeleteFiles; - set removeDeleteFiles(bool removeDeleteFiles) { - assert(removeDeleteFiles != null); - _removeDeleteFiles = removeDeleteFiles; - notifyListeners(); - } - - bool _removeAddExclusion = false; - bool get removeAddExclusion => _removeAddExclusion; - set removeAddExclusion(bool removeAddExclusion) { - assert(removeAddExclusion != null); - _removeAddExclusion = removeAddExclusion; - notifyListeners(); - } - - ///Navigation Indexes - - int _navigationIndex = 0; - int get navigationIndex => _navigationIndex; - set navigationIndex(int navigationIndex) { - assert(navigationIndex != null); - _navigationIndex = navigationIndex; - notifyListeners(); - } - - int _movieNavigationIndex = 0; - int get movieNavigationIndex => _movieNavigationIndex; - set movieNavigationIndex(int movieNavigationIndex) { - assert(movieNavigationIndex != null); - _movieNavigationIndex = movieNavigationIndex; - notifyListeners(); - } -} +export 'state/global.dart'; +export 'state/local.dart'; diff --git a/lib/modules/sonarr/core/state_global.dart b/lib/modules/radarr/core/state/global.dart similarity index 59% rename from lib/modules/sonarr/core/state_global.dart rename to lib/modules/radarr/core/state/global.dart index dc3597b4..1e15ddf0 100644 --- a/lib/modules/sonarr/core/state_global.dart +++ b/lib/modules/radarr/core/state/global.dart @@ -1,7 +1,10 @@ -import 'package:flutter/foundation.dart'; -import 'package:lunasea/modules/sonarr.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/radarr.dart'; -class SonarrModel extends ChangeNotifier { +class RadarrState extends LunaGlobalState { + @override + void reset() {} + ///Catalogue Sticky Header Content String _searchCatalogueFilter = ''; @@ -12,9 +15,10 @@ class SonarrModel extends ChangeNotifier { notifyListeners(); } - SonarrCatalogueSorting _sortCatalogueType = SonarrCatalogueSorting.alphabetical; - SonarrCatalogueSorting get sortCatalogueType => _sortCatalogueType; - set sortCatalogueType(SonarrCatalogueSorting sortCatalogueType) { + + RadarrCatalogueSorting _sortCatalogueType = RadarrCatalogueSorting.alphabetical; + RadarrCatalogueSorting get sortCatalogueType => _sortCatalogueType; + set sortCatalogueType(RadarrCatalogueSorting sortCatalogueType) { assert(sortCatalogueType != null); _sortCatalogueType = sortCatalogueType; notifyListeners(); @@ -28,11 +32,11 @@ class SonarrModel extends ChangeNotifier { notifyListeners(); } - bool _hideUnmonitoredSeries = false; - bool get hideUnmonitoredSeries => _hideUnmonitoredSeries; - set hideUnmonitoredSeries(bool hideUnmonitoredSeries) { - assert(hideUnmonitoredSeries != null); - _hideUnmonitoredSeries = hideUnmonitoredSeries; + bool _hideUnmonitoredMovies = false; + bool get hideUnmonitoredMovies => _hideUnmonitoredMovies; + set hideUnmonitoredMovies(bool hideUnmonitoredMovies) { + assert(hideUnmonitoredMovies != null); + _hideUnmonitoredMovies = hideUnmonitoredMovies; notifyListeners(); } @@ -46,9 +50,9 @@ class SonarrModel extends ChangeNotifier { notifyListeners(); } - SonarrReleasesSorting _sortReleasesType = SonarrReleasesSorting.weight; - SonarrReleasesSorting get sortReleasesType => _sortReleasesType; - set sortReleasesType(SonarrReleasesSorting sortReleasesType) { + RadarrReleasesSorting _sortReleasesType = RadarrReleasesSorting.weight; + RadarrReleasesSorting get sortReleasesType => _sortReleasesType; + set sortReleasesType(RadarrReleasesSorting sortReleasesType) { assert(sortReleasesType != null); _sortReleasesType = sortReleasesType; notifyListeners(); @@ -70,8 +74,8 @@ class SonarrModel extends ChangeNotifier { notifyListeners(); } - /// Add New Series Content - + ///Add New Movie Content + String _addSearchQuery = ''; String get addSearchQuery => _addSearchQuery; set addSearchQuery(String addSearchQuery) { @@ -80,6 +84,24 @@ class SonarrModel extends ChangeNotifier { notifyListeners(); } + ///Delete options + + bool _removeDeleteFiles = false; + bool get removeDeleteFiles => _removeDeleteFiles; + set removeDeleteFiles(bool removeDeleteFiles) { + assert(removeDeleteFiles != null); + _removeDeleteFiles = removeDeleteFiles; + notifyListeners(); + } + + bool _removeAddExclusion = false; + bool get removeAddExclusion => _removeAddExclusion; + set removeAddExclusion(bool removeAddExclusion) { + assert(removeAddExclusion != null); + _removeAddExclusion = removeAddExclusion; + notifyListeners(); + } + ///Navigation Indexes int _navigationIndex = 0; @@ -90,11 +112,11 @@ class SonarrModel extends ChangeNotifier { notifyListeners(); } - int _seriesNavigationIndex = 1; - int get seriesNavigationIndex => _seriesNavigationIndex; - set seriesNavigationIndex(int seriesNavigationIndex) { - assert(seriesNavigationIndex != null); - _seriesNavigationIndex = seriesNavigationIndex; + int _movieNavigationIndex = 0; + int get movieNavigationIndex => _movieNavigationIndex; + set movieNavigationIndex(int movieNavigationIndex) { + assert(movieNavigationIndex != null); + _movieNavigationIndex = movieNavigationIndex; notifyListeners(); } } diff --git a/lib/modules/radarr/core/state/local.dart b/lib/modules/radarr/core/state/local.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/modules/radarr/routes/add_details.dart b/lib/modules/radarr/routes/add_details.dart index 685ddaa3..d3fca101 100644 --- a/lib/modules/radarr/routes/add_details.dart +++ b/lib/modules/radarr/routes/add_details.dart @@ -92,7 +92,9 @@ class _State extends State { Widget get _appBar => _arguments == null ? null - : LSAppBar( + : LunaAppBar( + context: context, + popUntil: '/radarr', title: _arguments.data.title, actions: [ LSIconButton( @@ -137,7 +139,6 @@ class _State extends State { fallbackImage: 'assets/images/radarr/nomovieposter.png', headers: Database.currentProfileObject.getRadarr()['headers'], ), - LSDivider(), ValueListenableBuilder( valueListenable: Database.lunaSeaBox.listenable(keys: [RadarrDatabaseValue.ADD_MONITORED.key]), builder: (context, box, widget) { @@ -196,7 +197,6 @@ class _State extends State { ); }, ), - LSDivider(), LSContainerRow( children: [ Expanded( @@ -209,7 +209,7 @@ class _State extends State { Expanded( child: LSButton( text: 'Add + Search', - backgroundColor: LSColors.orange, + backgroundColor: LunaColours.orange, onTap: () async => _addMovie(true), reducedMargin: true, ), diff --git a/lib/modules/radarr/routes/add_search.dart b/lib/modules/radarr/routes/add_search.dart index 0ba25cb4..b4597242 100644 --- a/lib/modules/radarr/routes/add_search.dart +++ b/lib/modules/radarr/routes/add_search.dart @@ -44,7 +44,11 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Add Movie'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/radarr', + title: 'Add Movie', + ); Widget get _body => LSRefreshIndicator( refreshKey: _refreshKey, diff --git a/lib/modules/radarr/routes/details_movie.dart b/lib/modules/radarr/routes/details_movie.dart index b9726dd7..d9c55a5a 100644 --- a/lib/modules/radarr/routes/details_movie.dart +++ b/lib/modules/radarr/routes/details_movie.dart @@ -66,7 +66,9 @@ class _State extends State { : null, ); - Widget get _appBar => LSAppBar( + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/radarr', title: _arguments == null || _arguments.data == null ? 'Movie Details' : _arguments.data.title, diff --git a/lib/modules/radarr/routes/edit_movie.dart b/lib/modules/radarr/routes/edit_movie.dart index d6efb532..b51df15c 100644 --- a/lib/modules/radarr/routes/edit_movie.dart +++ b/lib/modules/radarr/routes/edit_movie.dart @@ -76,7 +76,11 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: _arguments?.data?.title ?? 'Edit Movie'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/radarr', + title: _arguments?.data?.title ?? 'Edit Movie', + ); Widget get _body => FutureBuilder( future: _future, @@ -130,7 +134,6 @@ class _State extends State { trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () => _changeMinimumAvailability(), ), - LSDivider(), LSButton( text: 'Update Movie', onTap: () async => _save().catchError((_) {}), @@ -139,7 +142,7 @@ class _State extends State { ); Future _changePath() async { - List _values = await GlobalDialogs.editText(context, 'Movie Path', prefill: _path); + List _values = await LunaDialogs.editText(context, 'Movie Path', prefill: _path); if(_values[0] && mounted) setState(() => _path = _values[1]); } diff --git a/lib/modules/radarr/routes/radarr.dart b/lib/modules/radarr/routes/radarr.dart index 83dd2de5..b7c24871 100644 --- a/lib/modules/radarr/routes/radarr.dart +++ b/lib/modules/radarr/routes/radarr.dart @@ -129,7 +129,7 @@ class _State extends State { _refreshAllPages(); break; } - default: Logger.warning('Radarr', '_enterAddMovie', 'Unknown Case: ${result[0]}'); + default: LunaLogger.warning('Radarr', '_enterAddMovie', 'Unknown Case: ${result[0]}'); } } @@ -156,7 +156,7 @@ class _State extends State { .catchError((_) => LSSnackBar(context: context, title: 'Failed to Search', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure)); break; } - default: Logger.warning('Radarr', '_handlePopup', 'Unknown Case: ${values[1]}'); + default: LunaLogger.warning('Radarr', '_handlePopup', 'Unknown Case: ${values[1]}'); } } diff --git a/lib/modules/radarr/routes/search_results.dart b/lib/modules/radarr/routes/search_results.dart index ef94e3ec..7bf78b42 100644 --- a/lib/modules/radarr/routes/search_results.dart +++ b/lib/modules/radarr/routes/search_results.dart @@ -55,7 +55,11 @@ class _State extends State { Widget get _appBar => _arguments == null ? null - : LSAppBar(title: _arguments.title); + : LunaAppBar( + context: context, + popUntil: '/radarr', + title: _arguments.title, + ); Widget get _body => _arguments == null ? null diff --git a/lib/modules/radarr/widgets/add_search_result_tile.dart b/lib/modules/radarr/widgets/add_search_result_tile.dart index 32f2c946..c0df3aa9 100644 --- a/lib/modules/radarr/widgets/add_search_result_tile.dart +++ b/lib/modules/radarr/widgets/add_search_result_tile.dart @@ -41,7 +41,7 @@ class RadarrAddSearchResultTile extends StatelessWidget { ); if(result != null) switch(result[0]) { case 'movie_added': Navigator.of(context).pop(result); break; - default: Logger.warning('RadarrAddSearchResultTile', '_enterDetails', 'Unknown Case: ${result[0]}'); + default: LunaLogger.warning('RadarrAddSearchResultTile', '_enterDetails', 'Unknown Case: ${result[0]}'); } } } diff --git a/lib/modules/radarr/widgets/catalogue_sorting_button.dart b/lib/modules/radarr/widgets/catalogue_sorting_button.dart index 4fa59781..73df9332 100644 --- a/lib/modules/radarr/widgets/catalogue_sorting_button.dart +++ b/lib/modules/radarr/widgets/catalogue_sorting_button.dart @@ -50,7 +50,7 @@ class _State extends State { ? Icons.arrow_upward : Icons.arrow_downward, size: Constants.UI_FONT_SIZE_SUBTITLE+2.0, - color: LSColors.accent, + color: LunaColours.accent, ), ], ), diff --git a/lib/modules/radarr/widgets/catalogue_tile.dart b/lib/modules/radarr/widgets/catalogue_tile.dart index 72271a30..d87765fa 100644 --- a/lib/modules/radarr/widgets/catalogue_tile.dart +++ b/lib/modules/radarr/widgets/catalogue_tile.dart @@ -61,7 +61,7 @@ class _State extends State { Icons.check_circle, size: 18.0, color: widget.data.downloaded ? - widget.data.monitored ? Color(Constants.ACCENT_COLOR) : Color(Constants.ACCENT_COLOR).withOpacity(0.30) : + widget.data.monitored ? Color(LunaColours.ACCENT_COLOR) : Color(LunaColours.ACCENT_COLOR).withOpacity(0.30) : widget.data.monitored ? Colors.grey : Colors.grey.withOpacity(0.30), ), padding: EdgeInsets.fromLTRB(0.0, 3.0, 16.0, 3.0), @@ -90,7 +90,6 @@ class _State extends State { customPadding: EdgeInsets.fromLTRB(12.0, 4.0, 12.0, 0.0), decoration: LSCardBackground( uri: widget.data.posterURI(), - darken: !widget.data.monitored, headers: Database.currentProfileObject.getRadarr()['headers'], ), ); @@ -137,7 +136,7 @@ class _State extends State { widget.refresh(); break; } - default: Logger.warning('RadarrCatalogueTile', '_enterMovie', 'Unknown Case: ${result[0]}'); + default: LunaLogger.warning('RadarrCatalogueTile', '_enterMovie', 'Unknown Case: ${result[0]}'); } } @@ -147,7 +146,7 @@ class _State extends State { case 'refresh_movie': _refreshMovie(); break; case 'edit_movie': _editMovie(); break; case 'remove_movie': _removeMovie(); break; - default: Logger.warning('RadarrCatalogueTile', '_handlePopup', 'Invalid method passed through popup. (${values[1]})'); + default: LunaLogger.warning('RadarrCatalogueTile', '_handlePopup', 'Invalid method passed through popup. (${values[1]})'); } } diff --git a/lib/modules/radarr/widgets/details_edit_button.dart b/lib/modules/radarr/widgets/details_edit_button.dart index 221ede66..f7d85206 100644 --- a/lib/modules/radarr/widgets/details_edit_button.dart +++ b/lib/modules/radarr/widgets/details_edit_button.dart @@ -30,7 +30,7 @@ class _State extends State { case 'refresh_movie': _refreshMovie(context); break; case 'edit_movie': _editMovie(context); break; case 'remove_movie': _removeMovie(context); break; - default: Logger.warning('RadarrDetailsEditButton', '_handlePopup', 'Invalid method passed through popup. (${values[1]})'); + default: LunaLogger.warning('RadarrDetailsEditButton', '_handlePopup', 'Invalid method passed through popup. (${values[1]})'); } } diff --git a/lib/modules/radarr/widgets/details_file_tile.dart b/lib/modules/radarr/widgets/details_file_tile.dart index 3a4bf39b..7156bb5e 100644 --- a/lib/modules/radarr/widgets/details_file_tile.dart +++ b/lib/modules/radarr/widgets/details_file_tile.dart @@ -43,7 +43,7 @@ class _State extends State { ), trailing: LSIconButton( icon: Icons.delete, - color: LSColors.red, + color: LunaColours.red, onPressed: () async => _delete().catchError((_) {}), ), padContent: true, diff --git a/lib/modules/radarr/widgets/details_overview.dart b/lib/modules/radarr/widgets/details_overview.dart index 527ef5f7..8cdedee5 100644 --- a/lib/modules/radarr/widgets/details_overview.dart +++ b/lib/modules/radarr/widgets/details_overview.dart @@ -35,7 +35,7 @@ class _State extends State with AutomaticKeepAliveClientM LSCardTile( title: LSTitle(text: 'Movie Path', centerText: true), subtitle: LSSubtitle(text: widget?.data?.path ?? 'Unknown', centerText: true), - onTap: () => GlobalDialogs.textPreview(context, widget?.data?.title, widget?.data?.path ?? 'Unknown'), + onTap: () => LunaDialogs.textPreview(context, widget?.data?.title, widget?.data?.path ?? 'Unknown'), ), LSContainerRow( children: [ diff --git a/lib/modules/radarr/widgets/details_search.dart b/lib/modules/radarr/widgets/details_search.dart index 388364f0..527ab0b8 100644 --- a/lib/modules/radarr/widgets/details_search.dart +++ b/lib/modules/radarr/widgets/details_search.dart @@ -27,7 +27,6 @@ class _State extends State with AutomaticKeepAliveClientMix Widget get _body => LSListView( children: [ _buttons, - LSDivider(), RadarrDetailsFileTile(data: widget.data), ], ); @@ -44,7 +43,7 @@ class _State extends State with AutomaticKeepAliveClientMix Expanded( child: LSButton( text: 'Interactive', - backgroundColor: LSColors.orange, + backgroundColor: LunaColours.orange, onTap: () async => _manual(), reducedMargin: true, ), diff --git a/lib/modules/radarr/widgets/details_search_tile.dart b/lib/modules/radarr/widgets/details_search_tile.dart index 994ce733..8afdc640 100644 --- a/lib/modules/radarr/widgets/details_search_tile.dart +++ b/lib/modules/radarr/widgets/details_search_tile.dart @@ -37,14 +37,14 @@ class RadarrSearchResultTile extends StatelessWidget { LSTextHighlighted( text: data.protocol.lsLanguage_Capitalize(), bgColor: data.isTorrent - ? LSColors.purple - : LSColors.blue, + ? LunaColours.purple + : LunaColours.blue, ), ...List.generate( data.customFormats.length, (index) => LSTextHighlighted( text: data.customFormats[index]['name'], - bgColor: LSColors.blueGrey, + bgColor: LunaColours.blueGrey, ), ), ], @@ -62,7 +62,7 @@ class RadarrSearchResultTile extends StatelessWidget { if(data.isTorrent) TextSpan( text: '${data.seeders} Seeders\t•\t${data.leechers} Leechers\n', style: TextStyle( - color: LSColors.purple, + color: LunaColours.purple, fontWeight: FontWeight.bold, ), ), @@ -91,7 +91,7 @@ class RadarrSearchResultTile extends StatelessWidget { if(!data.approved) Expanded( child: LSButtonSlim( text: 'Rejected', - backgroundColor: LSColors.red, + backgroundColor: LunaColours.red, onTap: () => _showWarnings(context), margin: EdgeInsets.only(left: 6.0), ), @@ -124,8 +124,8 @@ class RadarrSearchResultTile extends StatelessWidget { TextSpan( style: TextStyle( color: data.isTorrent - ? LSColors.purple - : LSColors.blue, + ? LunaColours.purple + : LunaColours.blue, fontWeight: FontWeight.bold, ), text: data.protocol.lsLanguage_Capitalize(), @@ -133,7 +133,7 @@ class RadarrSearchResultTile extends StatelessWidget { if(data.isTorrent) TextSpan( text: ' (${data.seeders}/${data.leechers})', style: TextStyle( - color: LSColors.purple, + color: LunaColours.purple, fontWeight: FontWeight.bold, ), ), @@ -150,7 +150,7 @@ class RadarrSearchResultTile extends StatelessWidget { : Icons.report, color: data.approved ? Colors.white - : LSColors.red, + : LunaColours.red, onPressed: () async => data.approved ? _startDownload(context) : _showWarnings(context), @@ -188,6 +188,6 @@ class RadarrSearchResultTile extends StatelessWidget { for(var i=0; i { ? Icons.arrow_upward : Icons.arrow_downward, size: Constants.UI_FONT_SIZE_SUBTITLE+2.0, - color: LSColors.accent, + color: LunaColours.accent, ), ], ), diff --git a/lib/modules/sabnzbd/core.dart b/lib/modules/sabnzbd/core.dart index e1155fe7..4f32a8f9 100644 --- a/lib/modules/sabnzbd/core.dart +++ b/lib/modules/sabnzbd/core.dart @@ -2,4 +2,4 @@ export 'core/api.dart'; export 'core/constants.dart'; export 'core/database.dart'; export 'core/dialogs.dart'; -export 'core/state_global.dart'; +export 'core/state.dart'; diff --git a/lib/modules/sabnzbd/core/api/api.dart b/lib/modules/sabnzbd/core/api/api.dart index 59f1278e..d4ba6d15 100644 --- a/lib/modules/sabnzbd/core/api/api.dart +++ b/lib/modules/sabnzbd/core/api/api.dart @@ -1,5 +1,3 @@ -import 'dart:io'; -import 'package:dio/adapter.dart'; import 'package:dio/dio.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/sabnzbd.dart'; @@ -23,21 +21,16 @@ class SABnzbdAPI extends API { maxRedirects: 5, ), ); - if(!profile.getSABnzbd()['strict_tls']) { - (_client.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) { - client.badCertificateCallback = (X509Certificate cert, String host, int port) => true; - }; - } return SABnzbdAPI._internal( profile.getSABnzbd(), _client, ); } - void logWarning(String methodName, String text) => Logger.warning('SABnzbdAPI', methodName, 'SABnzbd: $text'); + void logWarning(String methodName, String text) => LunaLogger.warning('SABnzbdAPI', methodName, 'SABnzbd: $text'); void logError(String methodName, String text, Object error, StackTrace trace, { bool uploadToSentry = true, - }) => Logger.error( + }) => LunaLogger.error( 'SABnzbdAPI', methodName, 'SABnzbd: $text', diff --git a/lib/modules/sabnzbd/core/api/data/history.dart b/lib/modules/sabnzbd/core/api/data/history.dart index 5db5883b..1fdde6c8 100644 --- a/lib/modules/sabnzbd/core/api/data/history.dart +++ b/lib/modules/sabnzbd/core/api/data/history.dart @@ -47,12 +47,12 @@ class SABnzbdHistoryData { Color get statusColor { switch(status.toLowerCase()) { - case 'completed': return LSColors.accent; - case 'queued': return LSColors.blue; - case 'extracting': return LSColors.orange; - case 'failed': return LSColors.red; + case 'completed': return LunaColours.accent; + case 'queued': return LunaColours.blue; + case 'extracting': return LunaColours.orange; + case 'failed': return LunaColours.red; } - return LSColors.purple; + return LunaColours.purple; } String get statusString { diff --git a/lib/modules/sabnzbd/core/constants.dart b/lib/modules/sabnzbd/core/constants.dart index 9f0ae34c..0c51740d 100644 --- a/lib/modules/sabnzbd/core/constants.dart +++ b/lib/modules/sabnzbd/core/constants.dart @@ -6,7 +6,7 @@ class SABnzbdConstants { static const String MODULE_KEY = 'sabnzbd'; - static const ModuleMap MODULE_MAP = ModuleMap( + static const LunaModuleMap MODULE_MAP = LunaModuleMap( name: 'SABnzbd', description: 'Manage Usenet Downloads', settingsDescription: 'Configure SABnzbd', diff --git a/lib/modules/sabnzbd/core/dialogs.dart b/lib/modules/sabnzbd/core/dialogs.dart index 4eedfc61..ae866640 100644 --- a/lib/modules/sabnzbd/core/dialogs.dart +++ b/lib/modules/sabnzbd/core/dialogs.dart @@ -31,7 +31,7 @@ class SABnzbdDialogs { (index) => LSDialog.tile( text: _options[index][0], icon: _options[index][1], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, _options[index][2]), ), ), @@ -68,7 +68,7 @@ class SABnzbdDialogs { (index) => LSDialog.tile( text: _options[index][0], icon: _options[index][1], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, _options[index][2]), ), ), @@ -100,7 +100,7 @@ class SABnzbdDialogs { (index) => LSDialog.tile( text: _options[index][0], icon: _options[index][1], - iconColor: _options.length == 1 ? LSColors.red : LSColors.list(index), + iconColor: _options.length == 1 ? LunaColours.red : LunaColours.list(index), onTap: () => _setValues(true, _options[index][2]), ), ), @@ -127,7 +127,7 @@ class SABnzbdDialogs { (index) => LSDialog.tile( text: categories[index].category, icon: Icons.category, - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, categories[index].category), ), ), @@ -166,7 +166,7 @@ class SABnzbdDialogs { (index) => LSDialog.tile( text: _options[index][0], icon: _options[index][1], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, _options[index][2], _options[index][3], _options[index][0]) ), ), @@ -196,7 +196,7 @@ class SABnzbdDialogs { (index) => LSDialog.tile( text: _options[index][0], icon: _options[index][1], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, _options[index][2]), ), ), @@ -350,7 +350,7 @@ class SABnzbdDialogs { (index) => LSDialog.tile( text: _options[index][0], icon: _options[index][1], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, _options[index][2]) ), ), @@ -386,7 +386,7 @@ class SABnzbdDialogs { (index) => LSDialog.tile( text: _options[index][0], icon: _options[index][1], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, _options[index][2]), ), ), @@ -475,7 +475,7 @@ class SABnzbdDialogs { (index) => LSDialog.tile( text: _options[index][0], icon: _options[index][1], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, _options[index][2], _options[index][0]), ), ), @@ -511,7 +511,7 @@ class SABnzbdDialogs { (index) => LSDialog.tile( text: _options[index][0], icon: _options[index][1], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, _options[index][2], _options[index][0]), ), ), @@ -548,7 +548,7 @@ class SABnzbdDialogs { (index) => LSDialog.tile( text: _options[index][0], icon: _options[index][1], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, _options[index][2], _options[index][3], _options[index][0]), ), ), @@ -616,7 +616,7 @@ class SABnzbdDialogs { buttons: [ LSDialog.button( text: 'Delete', - textColor: LSColors.red, + textColor: LunaColours.red, onPressed: () => _setValues(true), ), ], @@ -642,7 +642,7 @@ class SABnzbdDialogs { buttons: [ LSDialog.button( text: 'Delete', - textColor: LSColors.red, + textColor: LunaColours.red, onPressed: () => _setValues(true), ), ], @@ -672,7 +672,7 @@ class SABnzbdDialogs { (index) => LSDialog.tile( text: SABnzbdNavigationBar.titles[index], icon: SABnzbdNavigationBar.icons[index], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, index), ), ), diff --git a/lib/modules/sabnzbd/core/state.dart b/lib/modules/sabnzbd/core/state.dart new file mode 100644 index 00000000..ed5ebd8f --- /dev/null +++ b/lib/modules/sabnzbd/core/state.dart @@ -0,0 +1,2 @@ +export 'state/global.dart'; +export 'state/local.dart'; diff --git a/lib/modules/sabnzbd/core/state_global.dart b/lib/modules/sabnzbd/core/state/global.dart similarity index 92% rename from lib/modules/sabnzbd/core/state_global.dart rename to lib/modules/sabnzbd/core/state/global.dart index 18c79ac1..ba05b07c 100644 --- a/lib/modules/sabnzbd/core/state_global.dart +++ b/lib/modules/sabnzbd/core/state/global.dart @@ -1,6 +1,13 @@ -import 'package:flutter/foundation.dart'; +import 'package:lunasea/core.dart'; + +class SABnzbdState extends LunaGlobalState { + SABnzbdState() { + reset(); + } + + @override + void reset() {} -class SABnzbdModel extends ChangeNotifier { bool _error = false; bool get error => _error; set error(bool error) { diff --git a/lib/modules/sabnzbd/core/state/local.dart b/lib/modules/sabnzbd/core/state/local.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/modules/sabnzbd/routes/history.dart b/lib/modules/sabnzbd/routes/history.dart index 4576ae92..6bf8c416 100644 --- a/lib/modules/sabnzbd/routes/history.dart +++ b/lib/modules/sabnzbd/routes/history.dart @@ -35,7 +35,7 @@ class _State extends State with AutomaticKeepAliveClientMixin { _results = []; final _api = SABnzbdAPI.from(Database.currentProfileObject); if(mounted) setState(() { _future = _api.getHistory(); }); - Future.microtask(() => Provider.of(context, listen: false)?.historySearchFilter = ''); + Future.microtask(() => Provider.of(context, listen: false)?.historySearchFilter = ''); } @override @@ -84,7 +84,7 @@ class _State extends State with AutomaticKeepAliveClientMixin { buttonText: 'Refresh', onTapHandler: () => _refresh(), ) - : Selector>( + : Selector>( selector: (_, model) => Tuple2(model.historySearchFilter, model.historyHideFailed), builder: (context, data, _) { List _filtered = _filter(data.item1); diff --git a/lib/modules/sabnzbd/routes/history_stages.dart b/lib/modules/sabnzbd/routes/history_stages.dart index 95e53434..c315dc62 100644 --- a/lib/modules/sabnzbd/routes/history_stages.dart +++ b/lib/modules/sabnzbd/routes/history_stages.dart @@ -37,7 +37,11 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Stages'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/sabnzbd', + title: 'Stages', + ); Widget get _body => _arguments == null ? null @@ -50,7 +54,7 @@ class _State extends State { trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async { String _data = _arguments.data.stageLog[index]['actions'].join(',\n').replaceAll('
', '.\n'); - GlobalDialogs.textPreview(context, _arguments.data.stageLog[index]['name'], _data); + LunaDialogs.textPreview(context, _arguments.data.stageLog[index]['name'], _data); } ), ), diff --git a/lib/modules/sabnzbd/routes/queue.dart b/lib/modules/sabnzbd/routes/queue.dart index 07382fe3..d5a81ab2 100644 --- a/lib/modules/sabnzbd/routes/queue.dart +++ b/lib/modules/sabnzbd/routes/queue.dart @@ -82,7 +82,7 @@ class _State extends State with TickerProviderStateMixin, Automati Future _processStatus(SABnzbdStatusData data) async { if(mounted) { - final _model = Provider.of(context, listen: false); + final _model = Provider.of(context, listen: false); _model.paused = data.paused; _model.currentSpeed = data.currentSpeed; _model.queueSizeLeft = data.remainingSize; @@ -92,7 +92,7 @@ class _State extends State with TickerProviderStateMixin, Automati } void _setError(bool error) { - final _model = Provider.of(context, listen: false); + final _model = Provider.of(context, listen: false); _model.error = error; } diff --git a/lib/modules/sabnzbd/routes/sabnzbd.dart b/lib/modules/sabnzbd/routes/sabnzbd.dart index 42ab2927..99f457b6 100644 --- a/lib/modules/sabnzbd/routes/sabnzbd.dart +++ b/lib/modules/sabnzbd/routes/sabnzbd.dart @@ -25,7 +25,7 @@ class _State extends State { @override void initState() { super.initState(); - Future.microtask(() => Provider.of(context, listen: false).navigationIndex = 0); + Future.microtask(() => Provider.of(context, listen: false).navigationIndex = 0); } @override @@ -80,7 +80,7 @@ class _State extends State { }), actions: _api.enabled ? [ - Selector( + Selector( selector: (_, model) => model.error, builder: (context, error, widget) => error ? Container() @@ -103,7 +103,7 @@ class _State extends State { case 'clear_history': _clearHistory(); break; case 'complete_action': _completeAction(); break; case 'server_details': _serverDetails(); break; - default: Logger.warning('SABnzbd', '_handlePopup', 'Unknown Case: ${values[1]}'); + default: LunaLogger.warning('SABnzbd', '_handlePopup', 'Unknown Case: ${values[1]}'); } } @@ -171,7 +171,7 @@ class _State extends State { if(values[0]) switch(values[1]) { case 'link': _addByURL(); break; case 'file': _addByFile(); break; - default: Logger.warning('SABnzbd', '_addNZB', 'Unknown Case: ${values[1]}'); + default: LunaLogger.warning('SABnzbd', '_addNZB', 'Unknown Case: ${values[1]}'); } } @@ -234,7 +234,7 @@ class _State extends State { )); } - void _onPageChanged(int index) => Provider.of(context, listen: false).navigationIndex = index; + void _onPageChanged(int index) => Provider.of(context, listen: false).navigationIndex = index; void _refreshProfile() { _api = SABnzbdAPI.from(Database.currentProfileObject); diff --git a/lib/modules/sabnzbd/routes/statistics.dart b/lib/modules/sabnzbd/routes/statistics.dart index d04fa75f..b0356f6e 100644 --- a/lib/modules/sabnzbd/routes/statistics.dart +++ b/lib/modules/sabnzbd/routes/statistics.dart @@ -36,7 +36,11 @@ class _State extends State { }); } - Widget get _appBar => LSAppBar(title: 'Server Statistics'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/sabnzbd', + title: 'Server Statistics', + ); Widget get _body => LSRefreshIndicator( refreshKey: _refreshKey, diff --git a/lib/modules/sabnzbd/widgets/app_bar_stats.dart b/lib/modules/sabnzbd/widgets/app_bar_stats.dart index 72dd2b6b..f1894a4f 100644 --- a/lib/modules/sabnzbd/widgets/app_bar_stats.dart +++ b/lib/modules/sabnzbd/widgets/app_bar_stats.dart @@ -5,7 +5,7 @@ import 'package:lunasea/modules/sabnzbd.dart'; class SABnzbdAppBarStats extends StatelessWidget { @override - Widget build(BuildContext context) => Selector>( + Widget build(BuildContext context) => Selector>( selector: (_, model) => Tuple5( model.paused, //item1 model.currentSpeed, //item2 @@ -28,7 +28,7 @@ class SABnzbdAppBarStats extends StatelessWidget { style: TextStyle( fontWeight: FontWeight.bold, fontSize: Constants.UI_FONT_SIZE_HEADER, - color: LSColors.accent, + color: LunaColours.accent, ), ), TextSpan(text: '\n'), diff --git a/lib/modules/sabnzbd/widgets/history_hide_button.dart b/lib/modules/sabnzbd/widgets/history_hide_button.dart index 51f4599f..b45709cc 100644 --- a/lib/modules/sabnzbd/widgets/history_hide_button.dart +++ b/lib/modules/sabnzbd/widgets/history_hide_button.dart @@ -10,7 +10,7 @@ class SABnzbdHistoryHideButton extends StatefulWidget { class _State extends State { @override Widget build(BuildContext context) => LSCard( - child: Consumer( + child: Consumer( builder: (context, model, widget) => InkWell( child: LSIconButton( icon: model.historyHideFailed diff --git a/lib/modules/sabnzbd/widgets/history_search_bar.dart b/lib/modules/sabnzbd/widgets/history_search_bar.dart index bce9aec0..dade90c9 100644 --- a/lib/modules/sabnzbd/widgets/history_search_bar.dart +++ b/lib/modules/sabnzbd/widgets/history_search_bar.dart @@ -12,7 +12,7 @@ class _State extends State { @override Widget build(BuildContext context) => Expanded( - child: Consumer( + child: Consumer( builder: (context, model, widget) => LSTextInputBar( controller: _textController, labelText: 'Search History...', @@ -22,7 +22,7 @@ class _State extends State { ), ); - void _onChanged(SABnzbdModel model, String text, bool update) { + void _onChanged(SABnzbdState model, String text, bool update) { model.historySearchFilter = text; if(update) _textController.text = ''; } diff --git a/lib/modules/sabnzbd/widgets/history_tile.dart b/lib/modules/sabnzbd/widgets/history_tile.dart index 83451d45..4da693c3 100644 --- a/lib/modules/sabnzbd/widgets/history_tile.dart +++ b/lib/modules/sabnzbd/widgets/history_tile.dart @@ -86,7 +86,7 @@ class SABnzbdHistoryTile extends StatelessWidget { Expanded( child: LSButtonSlim( text: 'Delete', - backgroundColor: LSColors.red, + backgroundColor: LunaColours.red, onTap: () async => _delete(context), margin: EdgeInsets.only(left: 6.0), ), @@ -148,7 +148,7 @@ class SABnzbdHistoryTile extends StatelessWidget { ); if(result != null) switch(result[0]) { case 'delete': _handleRefresh(context, 'History Deleted'); break; - default: Logger.warning('SABnzbdHistoryTile', '_enterDetails', 'Unknown Case: ${result[0]}'); + default: LunaLogger.warning('SABnzbdHistoryTile', '_enterDetails', 'Unknown Case: ${result[0]}'); } } @@ -158,7 +158,7 @@ class SABnzbdHistoryTile extends StatelessWidget { case 'retry': _retry(context); break; case 'password': _password(context); break; case 'delete': _delete(context); break; - default: Logger.warning('SABnzbdHistoryTile', '_handlePopup', 'Unknown Case: ${values[1]}'); + default: LunaLogger.warning('SABnzbdHistoryTile', '_handlePopup', 'Unknown Case: ${values[1]}'); } } diff --git a/lib/modules/sabnzbd/widgets/navigation_bar.dart b/lib/modules/sabnzbd/widgets/navigation_bar.dart index 6516f921..ccfa1d5c 100644 --- a/lib/modules/sabnzbd/widgets/navigation_bar.dart +++ b/lib/modules/sabnzbd/widgets/navigation_bar.dart @@ -29,12 +29,12 @@ class _State extends State { void initState() { super.initState(); SchedulerBinding.instance.scheduleFrameCallback((_) { - Provider.of(context, listen: false).navigationIndex = SABnzbdDatabaseValue.NAVIGATION_INDEX.data; + Provider.of(context, listen: false).navigationIndex = SABnzbdDatabaseValue.NAVIGATION_INDEX.data; }); } @override - Widget build(BuildContext context) => Selector( + Widget build(BuildContext context) => Selector( selector: (_, model) => model.navigationIndex, builder: (context, index, _) => LSBottomNavigationBar( index: index, @@ -50,6 +50,6 @@ class _State extends State { duration: Duration(milliseconds: Constants.UI_NAVIGATION_SPEED), curve: Curves.easeOutSine, ); - Provider.of(context, listen: false).navigationIndex = index; + Provider.of(context, listen: false).navigationIndex = index; } } diff --git a/lib/modules/sabnzbd/widgets/queue_fab.dart b/lib/modules/sabnzbd/widgets/queue_fab.dart index 58aa6323..94a434e4 100644 --- a/lib/modules/sabnzbd/widgets/queue_fab.dart +++ b/lib/modules/sabnzbd/widgets/queue_fab.dart @@ -64,7 +64,7 @@ class _State extends State with TickerProviderStateMixin { } @override - Widget build(BuildContext context) => Selector>( + Widget build(BuildContext context) => Selector>( selector: (_, model) => Tuple2(model.error, model.paused), builder: (context, data, _) { data.item2 @@ -134,7 +134,7 @@ class _State extends State with TickerProviderStateMixin { _iconController.forward(); await api.pauseQueue() .then((_) { - Provider.of(context, listen: false).paused = true; + Provider.of(context, listen: false).paused = true; }) .catchError((_) { LSSnackBar( @@ -151,7 +151,7 @@ class _State extends State with TickerProviderStateMixin { _iconController.reverse(); return await api.resumeQueue() .then((_) { - Provider.of(context, listen: false).paused = false; + Provider.of(context, listen: false).paused = false; }) .catchError((_) { LSSnackBar( diff --git a/lib/modules/sabnzbd/widgets/queue_tile.dart b/lib/modules/sabnzbd/widgets/queue_tile.dart index a28e161d..9d1795ad 100644 --- a/lib/modules/sabnzbd/widgets/queue_tile.dart +++ b/lib/modules/sabnzbd/widgets/queue_tile.dart @@ -37,8 +37,8 @@ class _State extends State { child: LinearPercentIndicator( percent: min(1.0, max(0, widget.data.percentageDone/100)), padding: EdgeInsets.symmetric(horizontal: 2.0), - progressColor: widget.data.isPaused ? LSColors.accent.withOpacity(0.30) : LSColors.accent, - backgroundColor: widget.data.isPaused ? LSColors.accent.withOpacity(0.05) : LSColors.accent.withOpacity(0.15), + progressColor: widget.data.isPaused ? LunaColours.accent.withOpacity(0.30) : LunaColours.accent, + backgroundColor: widget.data.isPaused ? LunaColours.accent.withOpacity(0.05) : LunaColours.accent.withOpacity(0.15), lineHeight: 4.0, ), padding: EdgeInsets.symmetric(vertical: 6.0), @@ -69,7 +69,7 @@ class _State extends State { case 'password': _helper._password(); break; case 'rename': _helper._rename(); break; case 'delete': _helper._delete(); break; - default: Logger.warning('SABnzbdQueueTile', '_handlePopup', 'Unknown Case: ${values[1]}'); + default: LunaLogger.warning('SABnzbdQueueTile', '_handlePopup', 'Unknown Case: ${values[1]}'); } } } diff --git a/lib/modules/search/core.dart b/lib/modules/search/core.dart index 80320e6d..8a9fc449 100644 --- a/lib/modules/search/core.dart +++ b/lib/modules/search/core.dart @@ -3,4 +3,4 @@ export 'core/constants.dart'; export 'core/database.dart'; export 'core/dialogs.dart'; export 'core/sorting.dart'; -export 'core/state_global.dart'; +export 'core/state.dart'; diff --git a/lib/modules/search/core/api/api.dart b/lib/modules/search/core/api/api.dart index 005bcb1c..ff76f72e 100644 --- a/lib/modules/search/core/api/api.dart +++ b/lib/modules/search/core/api/api.dart @@ -35,7 +35,7 @@ class NewznabAPI extends API { String get host => _values['host']; String get key => _values['key']; - void logWarning(String methodName, String text) => Logger.warning( + void logWarning(String methodName, String text) => LunaLogger.warning( 'package:lunasea/core/api/newznab/api.dart', methodName, 'Newznab: $text', @@ -43,7 +43,7 @@ class NewznabAPI extends API { void logError(String methodName, String text, Object error, StackTrace trace, { bool uploadToSentry = true, - }) => Logger.error( + }) => LunaLogger.error( 'package:lunasea/core/api/newznab/api.dart', methodName, 'Newznab: $text', diff --git a/lib/modules/search/core/constants.dart b/lib/modules/search/core/constants.dart index 6f209227..a97607e9 100644 --- a/lib/modules/search/core/constants.dart +++ b/lib/modules/search/core/constants.dart @@ -6,13 +6,13 @@ class SearchConstants { static const String MODULE_KEY = 'search'; - static const ModuleMap MODULE_MAP = ModuleMap( + static const LunaModuleMap MODULE_MAP = LunaModuleMap( name: 'Search', description: 'Search Newznab Indexers', settingsDescription: 'Configure Search', icon: Icons.search, route: '/search', - color: Color(Constants.ACCENT_COLOR), + color: Color(LunaColours.ACCENT_COLOR), ); //ignore: non_constant_identifier_names diff --git a/lib/modules/search/core/dialogs.dart b/lib/modules/search/core/dialogs.dart index 92c11931..658f5119 100644 --- a/lib/modules/search/core/dialogs.dart +++ b/lib/modules/search/core/dialogs.dart @@ -19,7 +19,7 @@ class SearchDialogs { builder: (BuildContext context) => AlertDialog( title: LSDialog.title(text: 'Download'), actions: [ - LSDialog.cancel(context, textColor: LSColors.accent), + LSDialog.cancel(context, textColor: LunaColours.accent), ], content: ValueListenableBuilder( valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]), @@ -46,7 +46,7 @@ class SearchDialogs { ), LSIcon( icon: Icons.arrow_drop_down, - color: LSColors.accent, + color: LunaColours.accent, ), ], ), @@ -54,16 +54,13 @@ class SearchDialogs { decoration: BoxDecoration( border: Border( bottom: BorderSide( - color: LSColors.accent, + color: LunaColours.accent, width: 2.0, ), ), ), ), - onSelected: (result) { - LunaSeaDatabaseValue.ENABLED_PROFILE.put(result); - Providers.reset(context); - }, + onSelected: (result) => LunaProfile.changeProfile(context, result), itemBuilder: (context) { return >[for(String profile in (profilesBox as Box).keys) PopupMenuItem( value: profile, @@ -80,19 +77,19 @@ class SearchDialogs { ), if(Database.currentProfileObject.sabnzbdEnabled) LSDialog.tile( icon: CustomIcons.sabnzbd, - iconColor: LSColors.list(0), + iconColor: LunaColours.list(0), text: 'SABnzbd', onTap: () => _setValues(true, 'sabnzbd'), ), if(Database.currentProfileObject.nzbgetEnabled) LSDialog.tile( icon: CustomIcons.nzbget, - iconColor: LSColors.list(1), + iconColor: LunaColours.list(1), text: 'NZBGet', onTap: () => _setValues(true, 'nzbget'), ), LSDialog.tile( icon: Icons.file_download, - iconColor: LSColors.list(2), + iconColor: LunaColours.list(2), text: 'Download to Device', onTap: () => _setValues(true, 'filesystem'), ), diff --git a/lib/modules/search/core/sorting.dart b/lib/modules/search/core/sorting.dart index 8eb3c3bb..e8493b11 100644 --- a/lib/modules/search/core/sorting.dart +++ b/lib/modules/search/core/sorting.dart @@ -34,7 +34,7 @@ extension SearchResultsSortingExtension on SearchResultsSorting { ) => _sorter.byType(data, this, ascending); } -class _Sorter extends Sorter { +class _Sorter extends LunaSorter { @override List byType( List data, diff --git a/lib/modules/search/core/state.dart b/lib/modules/search/core/state.dart new file mode 100644 index 00000000..ed5ebd8f --- /dev/null +++ b/lib/modules/search/core/state.dart @@ -0,0 +1,2 @@ +export 'state/global.dart'; +export 'state/local.dart'; diff --git a/lib/modules/search/core/state_global.dart b/lib/modules/search/core/state/global.dart similarity index 94% rename from lib/modules/search/core/state_global.dart rename to lib/modules/search/core/state/global.dart index 7b4d028f..fcde3fee 100644 --- a/lib/modules/search/core/state_global.dart +++ b/lib/modules/search/core/state/global.dart @@ -1,8 +1,14 @@ -import 'package:flutter/foundation.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/search.dart'; -class SearchModel extends ChangeNotifier { +class SearchState extends LunaGlobalState { + SearchState() { + reset(); + } + + @override + void reset() {} + IndexerHiveObject _indexer; IndexerHiveObject get indexer => _indexer; set indexer(IndexerHiveObject indexer) { diff --git a/lib/modules/search/core/state/local.dart b/lib/modules/search/core/state/local.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/modules/search/routes/categories.dart b/lib/modules/search/routes/categories.dart index 0524864c..20f07eac 100644 --- a/lib/modules/search/routes/categories.dart +++ b/lib/modules/search/routes/categories.dart @@ -28,14 +28,16 @@ class _State extends State { ); Future _refresh() async { - final model = Provider.of(context, listen: false); + final model = Provider.of(context, listen: false); if(mounted) setState(() { _future = NewznabAPI.from(model?.indexer)?.getCategories(); }); } - Widget get _appBar => LSAppBar( - title: Provider.of(context, listen: false)?.indexer?.displayName ?? 'Categories', + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/search', + title: Provider.of(context, listen: false)?.indexer?.displayName ?? 'Categories', actions: [ LSIconButton( icon: Icons.search, @@ -79,7 +81,7 @@ class _State extends State { ); Future _enterSearch() async { - final model = Provider.of(context, listen: false); + final model = Provider.of(context, listen: false); model.searchTitle = '${model?.indexer?.displayName}'; model.searchCategoryID = -1; model.searchQuery = ''; diff --git a/lib/modules/search/routes/indexers.dart b/lib/modules/search/routes/indexers.dart index 43b0fbc2..3220e772 100644 --- a/lib/modules/search/routes/indexers.dart +++ b/lib/modules/search/routes/indexers.dart @@ -37,7 +37,12 @@ class _State extends State { ), ); - Widget get _appBar => LSAppBar(title: 'Search'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: null, + hideLeading: true, + title: 'Search', + ); Widget get _drawer => LSDrawer(page: 'search'); diff --git a/lib/modules/search/routes/results.dart b/lib/modules/search/routes/results.dart index 14e0b533..c728374c 100644 --- a/lib/modules/search/routes/results.dart +++ b/lib/modules/search/routes/results.dart @@ -31,16 +31,18 @@ class _State extends State { ); Future _refresh() async { - final model = Provider.of(context, listen: false); + final model = Provider.of(context, listen: false); if(mounted) setState(() => { _future = NewznabAPI.from(model?.indexer).getResults( categoryId: model?.searchCategoryID, query: '', )}); - Future.microtask(() => Provider.of(context, listen: false)?.searchResultsFilter = ''); + Future.microtask(() => Provider.of(context, listen: false)?.searchResultsFilter = ''); } - Widget get _appBar => LSAppBar( - title: Provider.of(context, listen: false)?.searchTitle ?? 'Results', + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/search', + title: Provider.of(context, listen: false)?.searchTitle ?? 'Results', actions: [ LSIconButton( icon: Icons.search, @@ -77,7 +79,7 @@ class _State extends State { buttonText: 'Refresh', onTapHandler: () => _refresh(), ) - : Consumer( + : Consumer( builder: (context, model, widget) { List _filtered = _sort(model, _filter(model.searchResultsFilter)); return _listBody(_filtered); @@ -112,7 +114,7 @@ class _State extends State { ); Future _enterSearch() async { - final model = Provider.of(context, listen: false); + final model = Provider.of(context, listen: false); model.searchQuery = ''; await Navigator.of(context).pushNamed(SearchSearch.ROUTE_NAME); } @@ -123,8 +125,8 @@ class _State extends State { : entry.title.toLowerCase().contains(filter.toLowerCase()) ).toList(); - List _sort(SearchModel model, List data) { - if(data != null && data.length != 0) return model.sortResultsSorting.sort(data, model.sortResultsAscending); + List _sort(SearchState state, List data) { + if(data != null && data.length != 0) return state.sortResultsSorting.sort(data, state.sortResultsAscending); return data; } } diff --git a/lib/modules/search/routes/search.dart b/lib/modules/search/routes/search.dart index 15f950a1..3ada9d3e 100644 --- a/lib/modules/search/routes/search.dart +++ b/lib/modules/search/routes/search.dart @@ -26,7 +26,7 @@ class _State extends State { } Future _refresh() async { - final model = Provider.of(context, listen: false); + final model = Provider.of(context, listen: false); if(mounted) setState(() { _future = NewznabAPI.from(model?.indexer).getResults( categoryId: model?.searchCategoryID, @@ -35,12 +35,16 @@ class _State extends State { }); } - Widget get _appBar => LSAppBar(title: 'Search: ${Provider.of(context, listen: false)?.searchTitle ?? 'Unknown'}'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/search', + title: 'Search: ${Provider.of(context, listen: false)?.searchTitle ?? 'Unknown'}', + ); Widget get _body => LSRefreshIndicator( refreshKey: _refreshKey, onRefresh: _refresh, - child: Consumer( + child: Consumer( builder: (context, model, widget) => FutureBuilder( future: _future, builder: (context, snapshot) { @@ -90,11 +94,11 @@ class _State extends State { List get _error => [LSErrorMessage(onTapHandler: () => _refresh(), hideButton: true)]; - List _assembleResults(SearchModel model) { + List _assembleResults(SearchState state) { if(_results.length <= 0) { return [LSGenericMessage(text: 'No Results Found')]; } - List _sorted = _sort(model, _results); + List _sorted = _sort(state, _results); return List.generate( _sorted.length, (index) => SearchResultTile( @@ -103,8 +107,8 @@ class _State extends State { ); } - List _sort(SearchModel model, List data) { - if(data != null && data.length != 0) return model.sortResultsSorting.sort(data, model.sortResultsAscending); + List _sort(SearchState state, List data) { + if(data != null && data.length != 0) return state.sortResultsSorting.sort(data, state.sortResultsAscending); return data; } } \ No newline at end of file diff --git a/lib/modules/search/routes/subcategories.dart b/lib/modules/search/routes/subcategories.dart index 158cfb99..1b73961d 100644 --- a/lib/modules/search/routes/subcategories.dart +++ b/lib/modules/search/routes/subcategories.dart @@ -19,8 +19,10 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar( - title: Provider.of(context, listen: false)?.category?.name ?? 'Subcategories', + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/search', + title: Provider.of(context, listen: false)?.category?.name ?? 'Subcategories', actions: [ LSIconButton( icon: Icons.search, @@ -29,7 +31,7 @@ class _State extends State { ], ); - Widget get _body => Consumer( + Widget get _body => Consumer( builder: (context, _state, child) => LSListViewBuilder( itemCount: (_state?.category?.subcategories?.length ?? 0)+1, itemBuilder: (context, index) => SearchSubcategoryTile( @@ -41,7 +43,7 @@ class _State extends State { ); Future _enterSearch() async { - final model = Provider.of(context, listen: false); + final model = Provider.of(context, listen: false); model.searchTitle = '${model?.category?.name ?? ''}'; model.searchCategoryID = model?.category?.id ?? 0; model.searchQuery = ''; diff --git a/lib/modules/search/widgets/category_tile.dart b/lib/modules/search/widgets/category_tile.dart index 35695af5..b17194d9 100644 --- a/lib/modules/search/widgets/category_tile.dart +++ b/lib/modules/search/widgets/category_tile.dart @@ -15,13 +15,13 @@ class SearchCategoryTile extends StatelessWidget { Widget build(BuildContext context) => LSCardTile( title: LSTitle(text: category.name), subtitle: LSSubtitle(text: category.subcategories.length == 0 ? 'No Subcategories Available': category.subcategoriesList), - leading: LSIconButton(icon: category.icon, color: LSColors.list(index)), + leading: LSIconButton(icon: category.icon, color: LunaColours.list(index)), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async => _enterSubcategories(context), ); Future _enterSubcategories(BuildContext context) async { - final model = Provider.of(context, listen: false); + final model = Provider.of(context, listen: false); model?.category = category; model?.searchCategoryID = category.id; await Navigator.of(context).pushNamed(SearchSubcategories.ROUTE_NAME); diff --git a/lib/modules/search/widgets/download_button.dart b/lib/modules/search/widgets/download_button.dart index 2d69f68f..e0835cf7 100644 --- a/lib/modules/search/widgets/download_button.dart +++ b/lib/modules/search/widgets/download_button.dart @@ -33,7 +33,7 @@ class SearchDetailsDownloadButton extends StatelessWidget { case 'sabnzbd': _sendToSABnzbd(context, data); break; case 'nzbget': _sendToNZBGet(context, data); break; case 'filesystem': _downloadToFilesystem(context); break; - default: Logger.warning('SearchDetailsDownloadButton', '_sendToClient', 'Unknown case: ${_values[1]}'); break; + default: LunaLogger.warning('SearchDetailsDownloadButton', '_sendToClient', 'Unknown case: ${_values[1]}'); break; } } } @@ -90,13 +90,13 @@ class SearchDetailsDownloadButton extends StatelessWidget { ), ).get(data.linkDownload); if(response.statusCode == 200) { - await Filesystem.exportDownloadToFilesystem('${data.title}.nzb', response.data); + await LunaFileSystem.exportDownloadToFilesystem('${data.title}.nzb', response.data); LSSnackBar(context: context, title: 'Downloaded NZB', message: 'Downloaded NZB to your device', type: SNACKBAR_TYPE.success); } else { throw Error(); } } catch (error) { - Logger.error('SearchDetailsDownloadButton', '_downloadToFilesystem', 'Error downloading NZB', error, StackTrace.current); + LunaLogger.error('SearchDetailsDownloadButton', '_downloadToFilesystem', 'Error downloading NZB', error, StackTrace.current); LSSnackBar(context: context, title: 'Failed to Download NZB', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure); } } diff --git a/lib/modules/search/widgets/indexer_tile.dart b/lib/modules/search/widgets/indexer_tile.dart index d3aab696..ab853b97 100644 --- a/lib/modules/search/widgets/indexer_tile.dart +++ b/lib/modules/search/widgets/indexer_tile.dart @@ -16,12 +16,12 @@ class SearchIndexerTile extends StatelessWidget { title: LSTitle(text: indexer.displayName), subtitle: LSSubtitle(text: indexer.host), trailing: LSIconButton(icon: Icons.arrow_forward_ios), - leading: LSIconButton(icon: Icons.rss_feed, color: LSColors.list(index)), + leading: LSIconButton(icon: Icons.rss_feed, color: LunaColours.list(index)), onTap: () async => _enterIndexer(context), ); Future _enterIndexer(BuildContext context) async { - Provider.of(context, listen: false)?.indexer = indexer; + Provider.of(context, listen: false)?.indexer = indexer; await Navigator.of(context).pushNamed(SearchCategories.ROUTE_NAME); } } diff --git a/lib/modules/search/widgets/result_search_bar.dart b/lib/modules/search/widgets/result_search_bar.dart index 488a21e6..80546f12 100644 --- a/lib/modules/search/widgets/result_search_bar.dart +++ b/lib/modules/search/widgets/result_search_bar.dart @@ -24,18 +24,18 @@ class _State extends State { @override Widget build(BuildContext context) => Expanded( - child: Consumer( - builder: (context, model, widget) => LSTextInputBar( + child: Consumer( + builder: (context, state, widget) => LSTextInputBar( controller: _textController, labelText: 'Search Results...', - onChanged: (text, update) => _onChanged(model, text, update), + onChanged: (text, update) => _onChanged(state, text, update), margin: EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 12.0), ), ), ); - void _onChanged(SearchModel model, String text, bool update) { - model.searchResultsFilter = text; + void _onChanged(SearchState state, String text, bool update) { + state.searchResultsFilter = text; if(update) _textController.text = ''; } } diff --git a/lib/modules/search/widgets/result_sorting_button.dart b/lib/modules/search/widgets/result_sorting_button.dart index fe8ab595..64fc8558 100644 --- a/lib/modules/search/widgets/result_sorting_button.dart +++ b/lib/modules/search/widgets/result_sorting_button.dart @@ -17,7 +17,7 @@ class SearchResultsSortButton extends StatefulWidget { class _State extends State { @override Widget build(BuildContext context) => LSCard( - child: Consumer( + child: Consumer( builder: (context, model, widget) => PopupMenuButton( shape: LunaSeaDatabaseValue.THEME_AMOLED.data && LunaSeaDatabaseValue.THEME_AMOLED_BORDER.data ? LSRoundedShapeWithBorder() @@ -50,7 +50,7 @@ class _State extends State { ? Icons.arrow_upward : Icons.arrow_downward, size: Constants.UI_FONT_SIZE_SUBTITLE+2.0, - color: LSColors.accent, + color: LunaColours.accent, ), ], ), diff --git a/lib/modules/search/widgets/search_bar.dart b/lib/modules/search/widgets/search_bar.dart index b6cb4672..d7a24299 100644 --- a/lib/modules/search/widgets/search_bar.dart +++ b/lib/modules/search/widgets/search_bar.dart @@ -19,13 +19,13 @@ class _State extends State { @override void initState() { super.initState(); - final model = Provider.of(context, listen: false); + final model = Provider.of(context, listen: false); _controller.text = model.searchQuery; } @override Widget build(BuildContext context) => Expanded( - child: Consumer( + child: Consumer( builder: (context, model, _) => LSTextInputBar( controller: _controller, onChanged: (text, updateController) => _onChange(model, text, updateController), @@ -35,8 +35,8 @@ class _State extends State { ), ); - void _onChange(SearchModel model, String text, updateController) { - model?.searchQuery = text; + void _onChange(SearchState state, String text, updateController) { + state?.searchQuery = text; if(updateController) _controller.text = text; } diff --git a/lib/modules/search/widgets/subcategory_tile.dart b/lib/modules/search/widgets/subcategory_tile.dart index 3b201755..c4288e6b 100644 --- a/lib/modules/search/widgets/subcategory_tile.dart +++ b/lib/modules/search/widgets/subcategory_tile.dart @@ -22,7 +22,7 @@ class SearchSubcategoryTile extends StatelessWidget { title: LSTitle(text: category?.subcategories[index ?? 0]?.name ?? 'Unknown'), subtitle: LSSubtitle(text: '${category?.name ?? 'Unknown'} > ${category?.subcategories[index ?? 0]?.name ?? 'Unknown'}'), trailing: LSIconButton(icon: Icons.arrow_forward_ios), - leading: LSIconButton(icon: category?.icon, color: LSColors.list(index+1)), + leading: LSIconButton(icon: category?.icon, color: LunaColours.list(index+1)), onTap: () => _enterResults( context, category?.subcategories[index ?? 0]?.id ?? 0, @@ -33,7 +33,7 @@ class SearchSubcategoryTile extends StatelessWidget { Widget _cardAll(BuildContext context) => LSCardTile( title: LSTitle(text: 'All Subcategories'), subtitle: LSSubtitle(text: '${category?.name} > All'), - leading: LSIconButton(icon: category?.icon, color: LSColors.list(0)), + leading: LSIconButton(icon: category?.icon, color: LunaColours.list(0)), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async => _enterResults( context, @@ -43,7 +43,7 @@ class SearchSubcategoryTile extends StatelessWidget { ); Future _enterResults(BuildContext context, int id, String title) async { - final model = Provider.of(context, listen: false); + final model = Provider.of(context, listen: false); model.searchTitle = title; model.searchCategoryID = id; Navigator.of(context).pushNamed(SearchResults.ROUTE_NAME); diff --git a/lib/modules/settings.dart b/lib/modules/settings.dart index 04e2ed23..217d5e0b 100644 --- a/lib/modules/settings.dart +++ b/lib/modules/settings.dart @@ -1,3 +1,2 @@ export 'settings/core.dart'; -export 'settings/main.dart'; export 'settings/modules.dart'; diff --git a/lib/modules/settings/core/constants.dart b/lib/modules/settings/core/constants.dart index 89e21217..761e1ce6 100644 --- a/lib/modules/settings/core/constants.dart +++ b/lib/modules/settings/core/constants.dart @@ -6,12 +6,12 @@ class SettingsConstants { static const String MODULE_KEY = 'settings'; - static const ModuleMap MODULE_MAP = ModuleMap( + static const LunaModuleMap MODULE_MAP = LunaModuleMap( name: 'Settings', description: 'Update Configuration', settingsDescription: '', icon: CustomIcons.settings, route: '/settings', - color: Color(Constants.ACCENT_COLOR), + color: Color(LunaColours.ACCENT_COLOR), ); } diff --git a/lib/modules/settings/core/dialogs.dart b/lib/modules/settings/core/dialogs.dart index cf632fe4..9993a6ff 100644 --- a/lib/modules/settings/core/dialogs.dart +++ b/lib/modules/settings/core/dialogs.dart @@ -135,7 +135,7 @@ class SettingsDialogs { (index) => LSDialog.tile( text: _options[index][0], icon: _options[index][1], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, _options[index][2]), ), ), @@ -267,7 +267,7 @@ class SettingsDialogs { LSDialog.button( text: 'Export', onPressed: () => _setValues(true), - textColor: LSColors.accent, + textColor: LunaColours.accent, ), ], content: [ @@ -302,7 +302,7 @@ class SettingsDialogs { LSDialog.button( text: 'Clear', onPressed: () => _setValues(true), - textColor: LSColors.red, + textColor: LunaColours.red, ), ], content: [ @@ -314,49 +314,6 @@ class SettingsDialogs { return [_flag]; } - static Future> toggleStrictTLS(BuildContext context) async { - bool _flag = false; - - void _setValues(bool flag) { - _flag = flag; - Navigator.of(context, rootNavigator: true).pop(); - } - - await LSDialog.dialog( - context: context, - title: 'Disable Strict SSL/TLS Validation', - buttons: [ - LSDialog.button( - text: 'Disable', - onPressed: () => _setValues(true), - textColor: LSColors.red, - ), - ], - content: [ - LSDialog.richText( - children: [ - LSDialog.bolded( - text: 'Please do not modify this setting unless you know what you are doing.\n\n', - color: LSColors.red, - fontSize: LSDialog.SUBBODY_SIZE, - ), - LSDialog.textSpanContent(text: 'Are you sure you want to disable strict SSL/TLS validation?\n\n'), - LSDialog.textSpanContent(text: 'Disabling strict SSL/TLS validation means that LunaSea will not validate the host machine\'s SSL certificate against a certificate authority.\n\n'), - LSDialog.textSpanContent(text: 'LunaSea will still connect to your host machine securely when using SSL/TLS whether strict SSL/TLS validation is enabled or disabled.\n\n'), - LSDialog.bolded( - text: 'Warning: Disabling strict SSL/TLS for an invalid or self-signed certificate will prevent a large amount of images from loading within LunaSea.', - color: LSColors.red, - fontSize: LSDialog.SUBBODY_SIZE, - ), - ], - alignment: TextAlign.center, - ), - ], - contentPadding: LSDialog.textDialogContentPadding(), - ); - return [_flag]; - } - static Future> nzbgetBasicAuthentication(BuildContext context) async { bool _flag = false; @@ -372,7 +329,7 @@ class SettingsDialogs { LSDialog.button( text: 'Use', onPressed: () => _setValues(true), - textColor: LSColors.red, + textColor: LunaColours.red, ), ], content: [ @@ -380,14 +337,14 @@ class SettingsDialogs { children: [ LSDialog.bolded( text: 'Please do not modify this setting unless you know what you are doing.\n\n', - color: LSColors.red, + color: LunaColours.red, fontSize: LSDialog.SUBBODY_SIZE, ), LSDialog.textSpanContent(text: 'Are you sure you want to use basic authentication to connect to NZBGet?\n\n'), LSDialog.textSpanContent(text: 'Basic authentication will add your username and password as a header in the request instead of encoding the details into the URL.\n\n'), LSDialog.bolded( text: 'Warning: This will allow you to have more complex passwords, but interfere with layered authentication methods.', - color: LSColors.red, + color: LunaColours.red, fontSize: LSDialog.SUBBODY_SIZE, ), ], @@ -453,7 +410,7 @@ class SettingsDialogs { profiles.length, (index) => LSDialog.tile( icon: Icons.settings, - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), text: profiles[index], onTap: () => _setValues(true, profiles[index]), ), @@ -479,7 +436,7 @@ class SettingsDialogs { context: context, title: 'Rename Profile', buttons: [ - LSDialog.button(text: 'Rename', onPressed: () => _setValues(true), textColor: LSColors.accent), + LSDialog.button(text: 'Rename', onPressed: () => _setValues(true), textColor: LunaColours.accent), ], content: [ Form( @@ -514,7 +471,7 @@ class SettingsDialogs { profiles.length, (index) => LSDialog.tile( icon: Icons.settings, - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), text: profiles[index], onTap: () => _setValues(true, profiles[index]), ), @@ -541,7 +498,7 @@ class SettingsDialogs { profiles.length, (index) => LSDialog.tile( icon: Icons.settings, - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), text: profiles[index], onTap: () => _setValues(true, profiles[index]), ), @@ -568,7 +525,7 @@ class SettingsDialogs { LSBrowsers.values.length, (index) => LSDialog.tile( icon: LSBrowsers.values[index].icon, - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), text: LSBrowsers.values[index].name, onTap: () => _setValues(true, LSBrowsers.values[index]), ), @@ -595,7 +552,7 @@ class SettingsDialogs { CalendarStartingDay.values.length, (index) => LSDialog.tile( icon: CustomIcons.calendar, - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), text: CalendarStartingDay.values[index].name, onTap: () => _setValues(true, CalendarStartingDay.values[index]), ), @@ -622,7 +579,7 @@ class SettingsDialogs { CalendarStartingSize.values.length, (index) => LSDialog.tile( icon: CalendarStartingSize.values[index].icon, - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), text: CalendarStartingSize.values[index].name, onTap: () => _setValues(true, CalendarStartingSize.values[index]), ), @@ -649,7 +606,7 @@ class SettingsDialogs { CalendarStartingType.values.length, (index) => LSDialog.tile( icon: CalendarStartingType.values[index].icon, - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), text: CalendarStartingType.values[index].name, onTap: () => _setValues(true, CalendarStartingType.values[index]), ), @@ -769,7 +726,7 @@ class SettingsDialogs { buttons: [ LSDialog.button( text: 'Reset', - textColor: LSColors.red, + textColor: LunaColours.red, onPressed: () => _setValues(true), ), ], @@ -841,7 +798,7 @@ class SettingsDialogs { buttons: [ LSDialog.button( text: 'Backup', - textColor: LSColors.accent, + textColor: LunaColours.accent, onPressed: () => _setValues(true), ), ], @@ -892,12 +849,12 @@ class SettingsDialogs { LSDialog.button( text: 'Sentry Website', onPressed: () => Constants.URL_SENTRY.lsLinks_OpenLink(), - textColor: LSColors.accent, + textColor: LunaColours.accent, ), LSDialog.button( text: 'Disable', onPressed: () => _setValues(true), - textColor: LSColors.red, + textColor: LunaColours.red, ), ], content: [ @@ -905,14 +862,14 @@ class SettingsDialogs { children: [ LSDialog.bolded( text: 'Error logs and stacktraces contain absolutely no identifying information on any users.\n\n', - color: LSColors.red, + color: LunaColours.red, fontSize: LSDialog.SUBBODY_SIZE, ), LSDialog.textSpanContent(text: 'Sentry is an open-source platform used for capturing crashes and errors.\n\n'), LSDialog.textSpanContent(text: 'To reserve your right to privacy, I have added the option to disable Sentry logging, but please know that these error logs and stacktraces are incredibly useful for catching and pinpointing bugs!\n\n'), LSDialog.bolded( text: 'A link to their website is available below for more information to help make an informed decision.', - color: LSColors.accent, + color: LunaColours.accent, fontSize: LSDialog.SUBBODY_SIZE, ), ], diff --git a/lib/modules/settings/core/router.dart b/lib/modules/settings/core/router.dart index 2af9ab12..f4eb72ca 100644 --- a/lib/modules/settings/core/router.dart +++ b/lib/modules/settings/core/router.dart @@ -2,58 +2,50 @@ import 'package:fluro_fork/fluro_fork.dart'; import 'package:lunasea/modules/settings.dart'; class SettingsRouter { - static final Router router = Router(); - SettingsRouter._(); - static void initialize() { - SettingsRoute.defineRoute(router); + static void initialize(Router router) { + SettingsHomeRouter.defineRoutes(router); // Customization - SettingsCustomizationRoute.defineRoute(router); - SettingsCustomizationAppearanceRoute.defineRoute(router); - SettingsCustomizationDrawerRoute.defineRoute(router); - SettingsCustomizationQuickActionsRoute.defineRoute(router); - SettingsCustomizationCalendarRoute.defineRoute(router); - SettingsCustomizationHomeRoute.defineRoute(router); - SettingsCustomizationSearchRoute.defineRoute(router); - SettingsCustomizationLidarrRoute.defineRoute(router); - SettingsCustomizationRadarrRoute.defineRoute(router); - SettingsCustomizationSonarrRoute.defineRoute(router); - SettingsCustomizationNZBGetRoute.defineRoute(router); - SettingsCustomizationSABnzbdRoute.defineRoute(router); - SettingsCustomizationTautulliRoute.defineRoute(router); + SettingsCustomizationRouter.defineRoutes(router); + SettingsCustomizationAppearanceRouter.defineRoutes(router); + SettingsCustomizationCalendarRouter.defineRoutes(router); + SettingsCustomizationDrawerRouter.defineRoutes(router); + SettingsCustomizationHomeRouter.defineRoutes(router); + SettingsCustomizationLidarrRouter.defineRoutes(router); + SettingsCustomizationNZBGetRouter.defineRoutes(router); + SettingsCustomizationQuickActionsRouter.defineRoutes(router); + SettingsCustomizationRadarrRouter.defineRoutes(router); + SettingsCustomizationSABnzbdRouter.defineRoutes(router); + SettingsCustomizationSearchRouter.defineRoutes(router); + SettingsCustomizationSonarrRouter.defineRoutes(router); + SettingsCustomizationTautulliRouter.defineRoutes(router); // Modules - SettingsModulesRoute.defineRoute(router); - SettingsModulesWakeOnLANRoute.defineRoute(router); - SettingsModulesSearchRoute.defineRoute(router); - SettingsModulesSearchAddRoute.defineRoute(router); - SettingsModulesSearchEditRoute.defineRoute(router); - SettingsModulesLidarrRoute.defineRoute(router); - SettingsModulesLidarrHeadersRoute.defineRoute(router); - SettingsModulesRadarrRoute.defineRoute(router); - SettingsModulesRadarrHeadersRoute.defineRoute(router); - SettingsModulesSonarrRoute.defineRoute(router); - SettingsModulesSonarrHeadersRoute.defineRoute(router); - SettingsModulesNZBGetRoute.defineRoute(router); - SettingsModulesNZBGetHeadersRoute.defineRoute(router); - SettingsModulesSABnzbdRoute.defineRoute(router); - SettingsModulesSABnzbdHeadersRoute.defineRoute(router); - SettingsModulesTautulliRoute.defineRoute(router); - SettingsModulesTautulliHeadersRoute.defineRoute(router); - // Profiles - SettingsProfilesRoute.defineRoute(router); - // --- - // Backup & Restore - SettingsBackupRestoreRoute.defineRoute(router); - // Donations - SettingsDonationsRoute.defineRoute(router); - SettingsDonationsThankYouRoute.defineRoute(router); - // Logs - SettingsLogsRoute.defineRoute(router); - SettingsLogsDetailsRoute.defineRoute(router); - // Resources - SettingsResourcesRoute.defineRoute(router); - // System - SettingsSystemRoute.defineRoute(router); + SettingsModulesRouter.defineRoutes(router); + SettingsModulesWakeOnLANRouter.defineRoutes(router); + SettingsModulesSearchRouter.defineRoutes(router); + SettingsModulesSearchAddRouter.defineRoutes(router); + SettingsModulesSearchEditRouter.defineRoutes(router); + SettingsModulesLidarrRouter.defineRoutes(router); + SettingsModulesLidarrHeadersRouter.defineRoutes(router); + SettingsModulesRadarrRouter.defineRoutes(router); + SettingsModulesRadarrHeadersRouter.defineRoutes(router); + SettingsModulesSonarrRouter.defineRoutes(router); + SettingsModulesSonarrHeadersRouter.defineRoutes(router); + SettingsModulesNZBGetRouter.defineRoutes(router); + SettingsModulesNZBGetHeadersRouter.defineRoutes(router); + SettingsModulesSABnzbdRouter.defineRoutes(router); + SettingsModulesSABnzbdHeadersRouter.defineRoutes(router); + SettingsModulesTautulliRouter.defineRoutes(router); + SettingsModulesTautulliHeadersRouter.defineRoutes(router); + // Other + SettingsProfilesRouter.defineRoutes(router); + SettingsBackupRestoreRouter.defineRoutes(router); + SettingsDonationsRouter.defineRoutes(router); + SettingsDonationsThankYouRouter.defineRoutes(router); + SettingsLogsRouter.defineRoutes(router); + SettingsLogsDetailsRouter.defineRoutes(router); + SettingsResourcesRouter.defineRoutes(router); + SettingsSystemRouter.defineRoutes(router); } } diff --git a/lib/modules/settings/core/state.dart b/lib/modules/settings/core/state.dart index 8493db5e..234eca2f 100644 --- a/lib/modules/settings/core/state.dart +++ b/lib/modules/settings/core/state.dart @@ -1 +1,6 @@ -export 'state/global.dart'; +import 'package:lunasea/core.dart'; + +class SettingsState extends LunaGlobalState { + @override + void reset() {} +} diff --git a/lib/modules/settings/core/state/global.dart b/lib/modules/settings/core/state/global.dart deleted file mode 100644 index feb26176..00000000 --- a/lib/modules/settings/core/state/global.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:flutter/material.dart'; - -class SettingsState extends ChangeNotifier { - SettingsState() { - reset(initialize: true); - } - - /// Reset the state of Home back to the default - /// - /// If `initialize` is true, resets everything. - /// If false, the navigation index, etc. are not reset. - void reset({ bool initialize = false }) { - if(initialize) {} - notifyListeners(); - } - - GlobalKey rootNavigatorKey = GlobalKey(); - GlobalKey rootScaffoldKey = GlobalKey(); -} diff --git a/lib/modules/settings/main.dart b/lib/modules/settings/main.dart deleted file mode 100644 index 836bbe91..00000000 --- a/lib/modules/settings/main.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/settings.dart'; - -class SettingsModule extends StatefulWidget { - static const String ROUTE_NAME = '/settings'; - - SettingsModule({ - Key key, - }) : super(key: key); - - @override - State createState() => _State(); -} - -class _State extends State { - @override - Widget build(BuildContext context) => WillPopScope( - onWillPop: _onWillPop, - child: Navigator( - key: Provider.of(context).rootNavigatorKey, - initialRoute: SettingsRoute.route(), - onGenerateRoute: SettingsRouter.router.generator, - ), - ); - - Future _onWillPop() async { - SettingsState _state = Provider.of(context, listen: false); - if(_state.rootNavigatorKey.currentState.canPop()) { - _state.rootNavigatorKey.currentState.pop(); - } else if(_state.rootScaffoldKey.currentState.hasDrawer) { - _state.rootScaffoldKey.currentState.isDrawerOpen - ? _state.rootNavigatorKey.currentState.pop() - : _state.rootScaffoldKey.currentState.openDrawer(); - } - return false; - } -} diff --git a/lib/modules/settings/modules/backuprestore/route.dart b/lib/modules/settings/modules/backuprestore/route.dart index b0e93ab6..dddc0556 100644 --- a/lib/modules/settings/modules/backuprestore/route.dart +++ b/lib/modules/settings/modules/backuprestore/route.dart @@ -3,21 +3,31 @@ import 'package:fluro_fork/fluro_fork.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsBackupRestoreRoute extends StatefulWidget { +class SettingsBackupRestoreRouter { static const ROUTE_NAME = '/settings/backuprestore'; + + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsBackupRestoreRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsBackupRestoreRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsBackupRestoreRouter._(); } -class _State extends State { +class _SettingsBackupRestoreRoute extends StatefulWidget { + @override + State<_SettingsBackupRestoreRoute> createState() => _State(); +} + +class _State extends State<_SettingsBackupRestoreRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override @@ -27,7 +37,11 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Backup & Restore'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', + title: 'Backup & Restore', + ); Widget get _body => LSListView( children: [ diff --git a/lib/modules/settings/modules/backuprestore/widgets/backup_tile.dart b/lib/modules/settings/modules/backuprestore/widgets/backup_tile.dart index 1b3b479c..23be2a97 100644 --- a/lib/modules/settings/modules/backuprestore/widgets/backup_tile.dart +++ b/lib/modules/settings/modules/backuprestore/widgets/backup_tile.dart @@ -16,9 +16,9 @@ class SettingsBackupRestoreBackupTile extends StatelessWidget { List _values = await SettingsDialogs.backupConfiguration(context); if(_values[0]) { String data = Export.export(); - String encrypted = Encryption.encrypt(_values[1], data); + String encrypted = LunaEncryption.encrypt(_values[1], data); if(encrypted != Constants.ENCRYPTION_FAILURE) { - await Filesystem.exportConfigToFilesystem(encrypted); + await LunaFileSystem.exportConfigToFilesystem(encrypted); LSSnackBar( context: context, title: 'Backed Up', @@ -28,7 +28,7 @@ class SettingsBackupRestoreBackupTile extends StatelessWidget { } } } catch (error) { - Logger.error('SettingsGeneralConfiguration', '_backup', 'Backup Failed', error, StackTrace.current); + LunaLogger.error('SettingsGeneralConfiguration', '_backup', 'Backup Failed', error, StackTrace.current); LSSnackBar( context: context, title: 'Back Up Failed', diff --git a/lib/modules/settings/modules/backuprestore/widgets/restore_tile.dart b/lib/modules/settings/modules/backuprestore/widgets/restore_tile.dart index 2b63b859..4e017629 100644 --- a/lib/modules/settings/modules/backuprestore/widgets/restore_tile.dart +++ b/lib/modules/settings/modules/backuprestore/widgets/restore_tile.dart @@ -21,9 +21,9 @@ class SettingsBackupRestoreRestoreTile extends StatelessWidget { String data = await file.readAsString(); List values = await SettingsDialogs.enterEncryptionKey(context); if(values[0]) { - String _decrypted = Encryption.decrypt(values[1], data); + String _decrypted = LunaEncryption.decrypt(values[1], data); if(_decrypted != Constants.ENCRYPTION_FAILURE) { - await Import.import(_decrypted) + await Import.import(context, _decrypted) ? LSSnackBar( context: context, title: 'Restored', @@ -36,7 +36,6 @@ class SettingsBackupRestoreRestoreTile extends StatelessWidget { message: 'This is not a valid LunaSea v2.x configuration backup', type: SNACKBAR_TYPE.failure, ); - Providers.reset(context); } else { LSSnackBar( context: context, @@ -55,7 +54,7 @@ class SettingsBackupRestoreRestoreTile extends StatelessWidget { ); } } catch (error) { - Logger.error('SettingsGeneralConfiguration', '_restore', 'Restore Failed', error, StackTrace.current); + LunaLogger.error('SettingsGeneralConfiguration', '_restore', 'Restore Failed', error, StackTrace.current); LSSnackBar( context: context, title: 'Failed to Restore', diff --git a/lib/modules/settings/modules/customization/route.dart b/lib/modules/settings/modules/customization/route.dart index a405b7b4..28148ec8 100644 --- a/lib/modules/settings/modules/customization/route.dart +++ b/lib/modules/settings/modules/customization/route.dart @@ -4,25 +4,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsCustomizationRoute extends StatefulWidget { +class SettingsCustomizationRouter { static const ROUTE_NAME = '/settings/customization'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsCustomizationRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsCustomizationRoute()), transitionType: LunaRouter.transitionType, ); - SettingsCustomizationRoute({ - Key key, - }): super(key: key); - - @override - State createState() => _State(); + SettingsCustomizationRouter._(); } -class _State extends State with AutomaticKeepAliveClientMixin { +class _SettingsCustomizationRoute extends StatefulWidget { + @override + State<_SettingsCustomizationRoute> createState() => _State(); +} + +class _State extends State<_SettingsCustomizationRoute> with AutomaticKeepAliveClientMixin { final GlobalKey _scaffoldKey = GlobalKey(); @override @@ -38,7 +44,11 @@ class _State extends State with AutomaticKeepAliveCl ); } - Widget get _appBar => LSAppBar(title: 'Customization'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', + title: 'Customization', + ); Widget get _body => LSListView( children: [ @@ -53,20 +63,20 @@ class _State extends State with AutomaticKeepAliveCl title: LSTitle(text: 'Appearance'), subtitle: LSSubtitle(text: 'Appearance Customizations'), trailing: LSIconButton(icon: Icons.brush), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsCustomizationAppearanceRoute.route()), + onTap: () async => SettingsCustomizationAppearanceRouter.navigateTo(context), ), LSCardTile( title: LSTitle(text: 'Drawer'), subtitle: LSSubtitle(text: 'Drawer Customizations'), trailing: LSIconButton(icon: Icons.dehaze), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsCustomizationDrawerRoute.route()), + onTap: () async => SettingsCustomizationDrawerRouter.navigateTo(context), ), if(Platform.isIOS) SettingsCustomizationBrowserTile(), LSCardTile( title: LSTitle(text: 'Quick Actions'), subtitle: LSSubtitle(text: 'Quick Actions on the Home Screen'), trailing: LSIconButton(icon: Icons.rounded_corner), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsCustomizationQuickActionsRoute.route()), + onTap: () async => SettingsCustomizationQuickActionsRouter.navigateTo(context), ), ]; @@ -75,58 +85,58 @@ class _State extends State with AutomaticKeepAliveCl title: LSTitle(text: 'Calendar'), subtitle: LSSubtitle(text: 'Calendar Customizations'), trailing: LSIconButton(icon: CustomIcons.calendar), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsCustomizationCalendarRoute.route()), + onTap: () async => SettingsCustomizationCalendarRouter.navigateTo(context), ), LSCardTile( title: LSTitle(text: 'Home'), subtitle: LSSubtitle(text: 'Home Customizations'), trailing: LSIconButton(icon: CustomIcons.home), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsCustomizationHomeRoute.route()), + onTap: () async => SettingsCustomizationHomeRouter.navigateTo(context), ), LSCardTile( title: LSTitle(text: 'Search'), subtitle: LSSubtitle(text: 'Search Customizations'), trailing: LSIconButton(icon: Icons.search), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsCustomizationSearchRoute.route()), + onTap: () async => SettingsCustomizationSearchRouter.navigateTo(context), ), LSDivider(), LSCardTile( title: LSTitle(text: 'Lidarr'), subtitle: LSSubtitle(text: 'Lidarr Customizations'), trailing: LSIconButton(icon: CustomIcons.music), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsCustomizationLidarrRoute.route()), + onTap: () async => SettingsCustomizationLidarrRouter.navigateTo(context), ), LSCardTile( title: LSTitle(text: 'Radarr'), subtitle: LSSubtitle(text: 'Radarr Customizations'), trailing: LSIconButton(icon: CustomIcons.movies), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsCustomizationRadarrRoute.route()), + onTap: () async => SettingsCustomizationRadarrRouter.navigateTo(context), ), LSCardTile( title: LSTitle(text: 'Sonarr'), subtitle: LSSubtitle(text: 'Sonarr Customizations'), trailing: LSIconButton(icon: CustomIcons.television), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsCustomizationSonarrRoute.route()), + onTap: () async => SettingsCustomizationSonarrRouter.navigateTo(context), ), LSDivider(), LSCardTile( title: LSTitle(text: 'NZBGet'), subtitle: LSSubtitle(text: 'NZBGet Customizations'), trailing: LSIconButton(icon: CustomIcons.nzbget), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsCustomizationNZBGetRoute.route()), + onTap: () async => SettingsCustomizationNZBGetRouter.navigateTo(context), ), LSCardTile( title: LSTitle(text: 'SABnzbd'), subtitle: LSSubtitle(text: 'SABnzbd Customizations'), trailing: LSIconButton(icon: CustomIcons.sabnzbd), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsCustomizationSABnzbdRoute.route()), + onTap: () async => SettingsCustomizationSABnzbdRouter.navigateTo(context), ), LSDivider(), LSCardTile( title: LSTitle(text: 'Tautulli'), subtitle: LSSubtitle(text: 'Tautulli Customizations'), trailing: LSIconButton(icon: CustomIcons.tautulli), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsCustomizationTautulliRoute.route()), + onTap: () async => SettingsCustomizationTautulliRouter.navigateTo(context), ), ]; } diff --git a/lib/modules/settings/modules/customization_appearance/route.dart b/lib/modules/settings/modules/customization_appearance/route.dart index b7c07281..af1cef08 100644 --- a/lib/modules/settings/modules/customization_appearance/route.dart +++ b/lib/modules/settings/modules/customization_appearance/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsCustomizationAppearanceRoute extends StatefulWidget { +class SettingsCustomizationAppearanceRouter { static const ROUTE_NAME = '/settings/customization/appearance'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsCustomizationAppearanceRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsCustomizationAppearanceRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsCustomizationAppearanceRouter._(); } -class _State extends State { +class _SettingsCustomizationAppearanceRoute extends StatefulWidget { + @override + State<_SettingsCustomizationAppearanceRoute> createState() => _State(); +} + +class _State extends State<_SettingsCustomizationAppearanceRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override Widget build(BuildContext context) => Scaffold( @@ -26,7 +36,11 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Appearance'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', + title: 'Appearance', + ); Widget get _body => LSListView( children: [ diff --git a/lib/modules/settings/modules/customization_calendar/route.dart b/lib/modules/settings/modules/customization_calendar/route.dart index c903e962..247071cb 100644 --- a/lib/modules/settings/modules/customization_calendar/route.dart +++ b/lib/modules/settings/modules/customization_calendar/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsCustomizationCalendarRoute extends StatefulWidget { +class SettingsCustomizationCalendarRouter { static const ROUTE_NAME = '/settings/customization/calendar'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsCustomizationCalendarRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsCustomizationCalendarRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsCustomizationCalendarRouter._(); } -class _State extends State { +class _SettingsCustomizationCalendarRoute extends StatefulWidget { + @override + State<_SettingsCustomizationCalendarRoute> createState() => _State(); +} + +class _State extends State<_SettingsCustomizationCalendarRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override Widget build(BuildContext context) => Scaffold( @@ -26,7 +36,11 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Calendar'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', + title: 'Calendar', + ); Widget get _body => LSListView( children: [ diff --git a/lib/modules/settings/modules/customization_drawer/route.dart b/lib/modules/settings/modules/customization_drawer/route.dart index 382b21fd..6ad76b99 100644 --- a/lib/modules/settings/modules/customization_drawer/route.dart +++ b/lib/modules/settings/modules/customization_drawer/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsCustomizationDrawerRoute extends StatefulWidget { +class SettingsCustomizationDrawerRouter { static const ROUTE_NAME = '/settings/customization/drawer'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsCustomizationDrawerRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsCustomizationDrawerRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsCustomizationDrawerRouter._(); } -class _State extends State { +class _SettingsCustomizationDrawerRoute extends StatefulWidget { + @override + State<_SettingsCustomizationDrawerRoute> createState() => _State(); +} + +class _State extends State<_SettingsCustomizationDrawerRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override Widget build(BuildContext context) => Scaffold( @@ -26,7 +36,11 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Drawer'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', + title: 'Drawer', + ); Widget get _body => LSListView( children: [ diff --git a/lib/modules/settings/modules/customization_home/route.dart b/lib/modules/settings/modules/customization_home/route.dart index fc8da72e..60ebc1cb 100644 --- a/lib/modules/settings/modules/customization_home/route.dart +++ b/lib/modules/settings/modules/customization_home/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsCustomizationHomeRoute extends StatefulWidget { +class SettingsCustomizationHomeRouter { static const ROUTE_NAME = '/settings/customization/home'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsCustomizationHomeRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsCustomizationHomeRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsCustomizationHomeRouter._(); } -class _State extends State { +class _SettingsCustomizationHomeRoute extends StatefulWidget { + @override + State<_SettingsCustomizationHomeRoute> createState() => _State(); +} + +class _State extends State<_SettingsCustomizationHomeRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override Widget build(BuildContext context) => Scaffold( @@ -26,7 +36,11 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Home'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', + title: 'Home', + ); Widget get _body => LSListView( children: [ diff --git a/lib/modules/settings/modules/customization_lidarr/route.dart b/lib/modules/settings/modules/customization_lidarr/route.dart index ff6054b3..d51a13c1 100644 --- a/lib/modules/settings/modules/customization_lidarr/route.dart +++ b/lib/modules/settings/modules/customization_lidarr/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsCustomizationLidarrRoute extends StatefulWidget { +class SettingsCustomizationLidarrRouter { static const ROUTE_NAME = '/settings/customization/lidarr'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsCustomizationLidarrRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsCustomizationLidarrRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsCustomizationLidarrRouter._(); } -class _State extends State { +class _SettingsCustomizationLidarrRoute extends StatefulWidget { + @override + State<_SettingsCustomizationLidarrRoute> createState() => _State(); +} + +class _State extends State<_SettingsCustomizationLidarrRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override Widget build(BuildContext context) => Scaffold( @@ -26,12 +36,14 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar( + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', title: 'Lidarr', actions: [ LSIconButton( icon: Icons.settings, - onPressed: () async => SettingsRouter.router.navigateTo(context, SettingsModulesLidarrRoute.route()), + onPressed: () async => SettingsModulesLidarrRouter.navigateTo(context), ), ] ); diff --git a/lib/modules/settings/modules/customization_nzbget/route.dart b/lib/modules/settings/modules/customization_nzbget/route.dart index 971437aa..d1ceb26b 100644 --- a/lib/modules/settings/modules/customization_nzbget/route.dart +++ b/lib/modules/settings/modules/customization_nzbget/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsCustomizationNZBGetRoute extends StatefulWidget { +class SettingsCustomizationNZBGetRouter { static const ROUTE_NAME = '/settings/customization/nzbget'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsCustomizationNZBGetRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsCustomizationNZBGetRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsCustomizationNZBGetRouter._(); } -class _State extends State { +class _SettingsCustomizationNZBGetRoute extends StatefulWidget { + @override + State<_SettingsCustomizationNZBGetRoute> createState() => _State(); +} + +class _State extends State<_SettingsCustomizationNZBGetRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override Widget build(BuildContext context) => Scaffold( @@ -26,12 +36,14 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar( + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', title: 'NZBGet', actions: [ LSIconButton( icon: Icons.settings, - onPressed: () async => SettingsRouter.router.navigateTo(context, SettingsModulesNZBGetRoute.route()), + onPressed: () async => SettingsModulesNZBGetRouter.navigateTo(context), ), ] ); diff --git a/lib/modules/settings/modules/customization_quickactions/route.dart b/lib/modules/settings/modules/customization_quickactions/route.dart index 05eadf8a..a5c6c94a 100644 --- a/lib/modules/settings/modules/customization_quickactions/route.dart +++ b/lib/modules/settings/modules/customization_quickactions/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsCustomizationQuickActionsRoute extends StatefulWidget { - static const ROUTE_NAME = '/settings/customization/quick_actions'; - static String route() => ROUTE_NAME; +class SettingsCustomizationQuickActionsRouter { + static const ROUTE_NAME = '/settings/customization/quickactions'; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsCustomizationQuickActionsRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsCustomizationQuickActionsRoute()), transitionType: LunaRouter.transitionType, ); - - @override - State createState() => _State(); + + SettingsCustomizationQuickActionsRouter._(); } -class _State extends State { +class _SettingsCustomizationQuickActionsRoute extends StatefulWidget { + @override + State<_SettingsCustomizationQuickActionsRoute> createState() => _State(); +} + +class _State extends State<_SettingsCustomizationQuickActionsRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override @@ -27,7 +37,11 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Quick Actions'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', + title: 'Quick Actions', + ); Widget get _body => LSListView( children: [ diff --git a/lib/modules/settings/modules/customization_quickactions/widgets/action_tile.dart b/lib/modules/settings/modules/customization_quickactions/widgets/action_tile.dart index 6289835f..8989ba6f 100644 --- a/lib/modules/settings/modules/customization_quickactions/widgets/action_tile.dart +++ b/lib/modules/settings/modules/customization_quickactions/widgets/action_tile.dart @@ -20,7 +20,7 @@ class SettingsCustomizationQuickActionTile extends StatelessWidget { value: action.data, onChanged: (value) { action.put(value); - HomescreenActions.setShortcutItems(); + LunaQuickActions.setShortcutItems(); } ), ), diff --git a/lib/modules/settings/modules/customization_radarr/route.dart b/lib/modules/settings/modules/customization_radarr/route.dart index 25b913be..66cda46a 100644 --- a/lib/modules/settings/modules/customization_radarr/route.dart +++ b/lib/modules/settings/modules/customization_radarr/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsCustomizationRadarrRoute extends StatefulWidget { +class SettingsCustomizationRadarrRouter { static const ROUTE_NAME = '/settings/customization/radarr'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsCustomizationRadarrRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsCustomizationRadarrRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsCustomizationRadarrRouter._(); } -class _State extends State { +class _SettingsCustomizationRadarrRoute extends StatefulWidget { + @override + State<_SettingsCustomizationRadarrRoute> createState() => _State(); +} + +class _State extends State<_SettingsCustomizationRadarrRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override Widget build(BuildContext context) => Scaffold( @@ -26,12 +36,14 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar( + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', title: 'Radarr', actions: [ LSIconButton( icon: Icons.settings, - onPressed: () async => SettingsRouter.router.navigateTo(context, SettingsModulesRadarrRoute.route()), + onPressed: () async => SettingsModulesRadarrRouter.navigateTo(context), ), ] ); diff --git a/lib/modules/settings/modules/customization_sabnzbd/route.dart b/lib/modules/settings/modules/customization_sabnzbd/route.dart index 6c3a1546..47d25af7 100644 --- a/lib/modules/settings/modules/customization_sabnzbd/route.dart +++ b/lib/modules/settings/modules/customization_sabnzbd/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsCustomizationSABnzbdRoute extends StatefulWidget { +class SettingsCustomizationSABnzbdRouter { static const ROUTE_NAME = '/settings/customization/sabnzbd'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsCustomizationSABnzbdRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsCustomizationSABnzbdRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsCustomizationSABnzbdRouter._(); } -class _State extends State { +class _SettingsCustomizationSABnzbdRoute extends StatefulWidget { + @override + State<_SettingsCustomizationSABnzbdRoute> createState() => _State(); +} + +class _State extends State<_SettingsCustomizationSABnzbdRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override Widget build(BuildContext context) => Scaffold( @@ -26,12 +36,14 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar( + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', title: 'SABnzbd', actions: [ LSIconButton( icon: Icons.settings, - onPressed: () async => SettingsRouter.router.navigateTo(context, SettingsModulesSABnzbdRoute.route()), + onPressed: () async => SettingsModulesSABnzbdRouter.navigateTo(context), ), ] ); diff --git a/lib/modules/settings/modules/customization_search/route.dart b/lib/modules/settings/modules/customization_search/route.dart index feb7773f..0fabd068 100644 --- a/lib/modules/settings/modules/customization_search/route.dart +++ b/lib/modules/settings/modules/customization_search/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsCustomizationSearchRoute extends StatefulWidget { +class SettingsCustomizationSearchRouter { static const ROUTE_NAME = '/settings/customization/search'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsCustomizationSearchRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsCustomizationSearchRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsCustomizationSearchRouter._(); } -class _State extends State { +class _SettingsCustomizationSearchRoute extends StatefulWidget { + @override + State<_SettingsCustomizationSearchRoute> createState() => _State(); +} + +class _State extends State<_SettingsCustomizationSearchRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override Widget build(BuildContext context) => Scaffold( @@ -26,12 +36,14 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar( + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', title: 'Search', actions: [ LSIconButton( icon: Icons.settings, - onPressed: () async => SettingsRouter.router.navigateTo(context, SettingsModulesSearchRoute.route()), + onPressed: () async => SettingsModulesSearchRouter.navigateTo(context), ), ] ); diff --git a/lib/modules/settings/modules/customization_sonarr/route.dart b/lib/modules/settings/modules/customization_sonarr/route.dart index 6e9f6d63..9fed926a 100644 --- a/lib/modules/settings/modules/customization_sonarr/route.dart +++ b/lib/modules/settings/modules/customization_sonarr/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsCustomizationSonarrRoute extends StatefulWidget { +class SettingsCustomizationSonarrRouter { static const ROUTE_NAME = '/settings/customization/sonarr'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsCustomizationSonarrRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsCustomizationSonarrRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsCustomizationSonarrRouter._(); } -class _State extends State { +class _SettingsCustomizationSonarrRoute extends StatefulWidget { + @override + State<_SettingsCustomizationSonarrRoute> createState() => _State(); +} + +class _State extends State<_SettingsCustomizationSonarrRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override Widget build(BuildContext context) => Scaffold( @@ -26,12 +36,14 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar( + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', title: 'Sonarr', actions: [ LSIconButton( icon: Icons.settings, - onPressed: () async => SettingsRouter.router.navigateTo(context, SettingsModulesSonarrRoute.route()), + onPressed: () async => SettingsModulesSonarrRouter.navigateTo(context), ), ] ); @@ -42,7 +54,8 @@ class _State extends State { text: 'Default Pages', subtitle: 'Choose the default page when opening routes with navigation bars', ), - SettingsCustomizationSonarrDefaultPageTile(), + SettingsCustomizationSonarrDefaultPageHomeTile(), + SettingsCustomizationSonarrDefaultPageSeriesDetailsTile(), ], ); } diff --git a/lib/modules/settings/modules/customization_sonarr/widgets.dart b/lib/modules/settings/modules/customization_sonarr/widgets.dart index cf719210..6d62a860 100644 --- a/lib/modules/settings/modules/customization_sonarr/widgets.dart +++ b/lib/modules/settings/modules/customization_sonarr/widgets.dart @@ -1 +1,2 @@ -export 'widgets/default_page_tile.dart'; +export 'widgets/default_pages_home.dart'; +export 'widgets/default_pages_series_details.dart'; diff --git a/lib/modules/settings/modules/customization_sonarr/widgets/default_page_tile.dart b/lib/modules/settings/modules/customization_sonarr/widgets/default_pages_home.dart similarity index 78% rename from lib/modules/settings/modules/customization_sonarr/widgets/default_page_tile.dart rename to lib/modules/settings/modules/customization_sonarr/widgets/default_pages_home.dart index 5f3ccc55..b9a83133 100644 --- a/lib/modules/settings/modules/customization_sonarr/widgets/default_page_tile.dart +++ b/lib/modules/settings/modules/customization_sonarr/widgets/default_pages_home.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/sonarr.dart'; -class SettingsCustomizationSonarrDefaultPageTile extends StatelessWidget { +class SettingsCustomizationSonarrDefaultPageHomeTile extends StatelessWidget { @override Widget build(BuildContext context) => ValueListenableBuilder( valueListenable: Database.lunaSeaBox.listenable(keys: [SonarrDatabaseValue.NAVIGATION_INDEX.key]), @@ -15,7 +15,7 @@ class SettingsCustomizationSonarrDefaultPageTile extends StatelessWidget { ); Future _defaultPage(BuildContext context) async { - List _values = await SonarrDialogs.defaultPage(context); + List _values = await SonarrDialogs.setDefaultPage(context, titles: SonarrNavigationBar.titles, icons: SonarrNavigationBar.icons); if(_values[0]) SonarrDatabaseValue.NAVIGATION_INDEX.put(_values[1]); } } diff --git a/lib/modules/settings/modules/customization_sonarr/widgets/default_pages_series_details.dart b/lib/modules/settings/modules/customization_sonarr/widgets/default_pages_series_details.dart new file mode 100644 index 00000000..213a921a --- /dev/null +++ b/lib/modules/settings/modules/customization_sonarr/widgets/default_pages_series_details.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SettingsCustomizationSonarrDefaultPageSeriesDetailsTile extends StatelessWidget { + @override + Widget build(BuildContext context) => ValueListenableBuilder( + valueListenable: Database.lunaSeaBox.listenable(keys: [SonarrDatabaseValue.NAVIGATION_INDEX_SERIES_DETAILS.key]), + builder: (context, box, _) => LSCardTile( + title: LSTitle(text: 'Series Details'), + subtitle: LSSubtitle(text: SonarrSeriesDetailsNavigationBar.titles[SonarrDatabaseValue.NAVIGATION_INDEX_SERIES_DETAILS.data]), + trailing: LSIconButton(icon: SonarrSeriesDetailsNavigationBar.icons[SonarrDatabaseValue.NAVIGATION_INDEX_SERIES_DETAILS.data]), + onTap: () async => _defaultPage(context), + ), + ); + + Future _defaultPage(BuildContext context) async { + List _values = await SonarrDialogs.setDefaultPage(context, titles: SonarrSeriesDetailsNavigationBar.titles, icons: SonarrSeriesDetailsNavigationBar.icons); + if(_values[0]) SonarrDatabaseValue.NAVIGATION_INDEX_SERIES_DETAILS.put(_values[1]); + } +} diff --git a/lib/modules/settings/modules/customization_tautulli/route.dart b/lib/modules/settings/modules/customization_tautulli/route.dart index f9f97004..88d89de6 100644 --- a/lib/modules/settings/modules/customization_tautulli/route.dart +++ b/lib/modules/settings/modules/customization_tautulli/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsCustomizationTautulliRoute extends StatefulWidget { +class SettingsCustomizationTautulliRouter { static const ROUTE_NAME = '/settings/customization/tautulli'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsCustomizationTautulliRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsCustomizationTautulliRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsCustomizationTautulliRouter._(); } -class _State extends State { +class _SettingsCustomizationTautulliRoute extends StatefulWidget { + @override + State<_SettingsCustomizationTautulliRoute> createState() => _State(); +} + +class _State extends State<_SettingsCustomizationTautulliRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override Widget build(BuildContext context) => Scaffold( @@ -26,12 +36,14 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar( + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', title: 'Tautulli', actions: [ LSIconButton( icon: Icons.settings, - onPressed: () async => SettingsRouter.router.navigateTo(context, SettingsModulesTautulliRoute.route()), + onPressed: () async => SettingsModulesTautulliRouter.navigateTo(context), ), ] ); diff --git a/lib/modules/settings/modules/donations/route.dart b/lib/modules/settings/modules/donations/route.dart index ae8278e0..b6f0caf2 100644 --- a/lib/modules/settings/modules/donations/route.dart +++ b/lib/modules/settings/modules/donations/route.dart @@ -5,32 +5,38 @@ import 'package:in_app_purchase/in_app_purchase.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsDonationsRoute extends StatefulWidget { +class SettingsDonationsRouter { static const ROUTE_NAME = '/settings/donations'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsDonationsRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsDonationsRoute()), transitionType: LunaRouter.transitionType, ); - SettingsDonationsRoute({ - Key key, - }): super(key: key); - - @override - State createState() => _State(); + SettingsDonationsRouter._(); } -class _State extends State { +class _SettingsDonationsRoute extends StatefulWidget { + @override + State<_SettingsDonationsRoute> createState() => _State(); +} + +class _State extends State<_SettingsDonationsRoute> { final GlobalKey _scaffoldKey = GlobalKey(); static StreamSubscription> purchaseStream; @override void initState() { super.initState(); - purchaseStream = InAppPurchases.connection.purchaseUpdatedStream.listen(_purchasedCallback); + purchaseStream = LunaInAppPurchases.connection.purchaseUpdatedStream.listen(_purchasedCallback); } @override @@ -51,7 +57,7 @@ class _State extends State { } } - void _purchasedSuccess() => SettingsRouter.router.navigateTo(context, SettingsDonationsThankYouRoute.route()); + void _purchasedSuccess() => SettingsDonationsThankYouRouter.navigateTo(context); void _purchaseFailed() => LSSnackBar( context: context, @@ -67,12 +73,16 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Donations'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', + title: 'Donations', + ); - Widget get _body => InAppPurchases.available && InAppPurchases.products.length != 0 + Widget get _body => LunaInAppPurchases.available && LunaInAppPurchases.products.length != 0 ? LSListViewBuilder( - itemCount: InAppPurchases.products.length, - itemBuilder: (context, index) => SettingsDonationsIAPTile(product: InAppPurchases.products[index]), + itemCount: LunaInAppPurchases.products.length, + itemBuilder: (context, index) => SettingsDonationsIAPTile(product: LunaInAppPurchases.products[index]), ) : LSGenericMessage(text: 'In-App Purchases Unavailable'); } diff --git a/lib/modules/settings/modules/donations/widgets/iap_tile.dart b/lib/modules/settings/modules/donations/widgets/iap_tile.dart index a4d3b3a2..3c35a79b 100644 --- a/lib/modules/settings/modules/donations/widgets/iap_tile.dart +++ b/lib/modules/settings/modules/donations/widgets/iap_tile.dart @@ -20,6 +20,6 @@ class SettingsDonationsIAPTile extends StatelessWidget { Future _purchase() async { final PurchaseParam _parameters = PurchaseParam(productDetails: product, sandboxTesting: false); - await InAppPurchases.connection.buyConsumable(purchaseParam: _parameters, autoConsume: true); + await LunaInAppPurchases.connection.buyConsumable(purchaseParam: _parameters, autoConsume: true); } } \ No newline at end of file diff --git a/lib/modules/settings/modules/donations_thankyou/route.dart b/lib/modules/settings/modules/donations_thankyou/route.dart index 037998af..5d159a8c 100644 --- a/lib/modules/settings/modules/donations_thankyou/route.dart +++ b/lib/modules/settings/modules/donations_thankyou/route.dart @@ -3,25 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:flare_flutter/flare_actor.dart'; -class SettingsDonationsThankYouRoute extends StatefulWidget { +class SettingsDonationsThankYouRouter { static const ROUTE_NAME = '/settings/donations/thankyou'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsDonationsThankYouRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsDonationsThankYouRoute()), transitionType: LunaRouter.transitionType, ); - SettingsDonationsThankYouRoute({ - Key key, - }): super(key: key); - - @override - State createState() => _State(); + SettingsDonationsThankYouRouter._(); } -class _State extends State { +class _SettingsDonationsThankYouRoute extends StatefulWidget { + @override + State<_SettingsDonationsThankYouRoute> createState() => _State(); +} + +class _State extends State<_SettingsDonationsThankYouRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override @@ -31,7 +37,11 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Donations'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', + title: 'Donations', + ); Widget get _body => Padding( child: Column( diff --git a/lib/modules/settings/modules/logs/route.dart b/lib/modules/settings/modules/logs/route.dart index da20f98e..bb9c4749 100644 --- a/lib/modules/settings/modules/logs/route.dart +++ b/lib/modules/settings/modules/logs/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsLogsRoute extends StatefulWidget { +class SettingsLogsRouter { static const ROUTE_NAME = '/settings/logs'; + + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsLogsRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsLogsRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsLogsRouter._(); } -class _State extends State { +class _SettingsLogsRoute extends StatefulWidget { + @override + State<_SettingsLogsRoute> createState() => _State(); +} + +class _State extends State<_SettingsLogsRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override @@ -27,7 +37,11 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Logs'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', + title: 'Logs', + ); Widget get _body => LSListView( children: [ @@ -69,8 +83,8 @@ class _State extends State { SettingsLogsClearTile(), ]; - Future _viewLogs(String type) async => SettingsRouter.router.navigateTo( + Future _viewLogs(String type) async => SettingsLogsDetailsRouter.navigateTo( context, - SettingsLogsDetailsRoute.route(type: type), + type: type, ); } diff --git a/lib/modules/settings/modules/logs/widgets/clear_tile.dart b/lib/modules/settings/modules/logs/widgets/clear_tile.dart index bb6e9d84..fde3fbc4 100644 --- a/lib/modules/settings/modules/logs/widgets/clear_tile.dart +++ b/lib/modules/settings/modules/logs/widgets/clear_tile.dart @@ -14,7 +14,7 @@ class SettingsLogsClearTile extends StatelessWidget { Future _clearLogs(BuildContext context) async { List _values = await SettingsDialogs.clearLogs(context); if(_values[0]) { - Logger.clearLogs(); + LunaLogger.clearLogs(); LSSnackBar(context: context, title: 'Logs Cleared', message: 'All recorded logs have been cleared', type: SNACKBAR_TYPE.success); } } diff --git a/lib/modules/settings/modules/logs/widgets/export_tile.dart b/lib/modules/settings/modules/logs/widgets/export_tile.dart index 11acd803..12740228 100644 --- a/lib/modules/settings/modules/logs/widgets/export_tile.dart +++ b/lib/modules/settings/modules/logs/widgets/export_tile.dart @@ -14,7 +14,7 @@ class SettingsLogsExportTile extends StatelessWidget { Future _exportLogs(BuildContext context) async { List _values = await SettingsDialogs.exportLogs(context); if(_values[0]) { - Logger.exportLogs(); + LunaLogger.exportLogs(); LSSnackBar(context: context, title: 'Exported Logs', message: 'Logs are located in the application directory', type: SNACKBAR_TYPE.success); } } diff --git a/lib/modules/settings/modules/logs_details/route.dart b/lib/modules/settings/modules/logs_details/route.dart index 5c3d6f94..3e1688a9 100644 --- a/lib/modules/settings/modules/logs_details/route.dart +++ b/lib/modules/settings/modules/logs_details/route.dart @@ -4,32 +4,44 @@ import 'package:f_logs/f_logs.dart' as FLog; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsLogsDetailsRoute extends StatefulWidget { - final String type; - +class SettingsLogsDetailsRouter { static const ROUTE_NAME = '/settings/logs/details/:type'; + + static Future navigateTo(BuildContext context, { + @required String type, + }) async => LunaRouter.router.navigateTo( + context, + route(type: type), + ); + static String route({ @required String type, }) => ROUTE_NAME.replaceFirst(':type', type); - - static void defineRoute(Router router) => router.define( + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsLogsDetailsRoute( - type: params['type'][0], + handler: Handler(handlerFunc: (context, params) => _SettingsLogsDetailsRoute( + type: params['type'] == null ? 'All' : params['type'][0], )), transitionType: LunaRouter.transitionType, ); - SettingsLogsDetailsRoute({ + SettingsLogsDetailsRouter._(); +} + +class _SettingsLogsDetailsRoute extends StatefulWidget { + final String type; + + _SettingsLogsDetailsRoute({ Key key, @required this.type, }) : super(key: key); @override - State createState() => _State(); + State<_SettingsLogsDetailsRoute> createState() => _State(); } -class _State extends State { +class _State extends State<_SettingsLogsDetailsRoute> { final GlobalKey _scaffoldKey = GlobalKey(); List levels = []; @@ -56,7 +68,11 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: '${widget.type ?? 'Unknown'} Logs'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', + title: '${widget.type ?? 'Unknown'} Logs', + ); Widget get _body => FutureBuilder( future: FLog.FLog.getAllLogsByFilter(logLevels: levels), @@ -64,8 +80,8 @@ class _State extends State { switch(snapshot.connectionState) { case ConnectionState.done: if(snapshot.hasError) { - Logger.error( - 'SettingsLogsDetailsRoute', + LunaLogger.error( + '_SettingsLogsDetailsRoute', '_body', 'Unable to fetch logs', snapshot.error, diff --git a/lib/modules/settings/modules/logs_details/widgets/log_tile.dart b/lib/modules/settings/modules/logs_details/widgets/log_tile.dart index bc19be7f..d84e8af8 100644 --- a/lib/modules/settings/modules/logs_details/widgets/log_tile.dart +++ b/lib/modules/settings/modules/logs_details/widgets/log_tile.dart @@ -92,16 +92,16 @@ class SettingsLogsDetailsLogTile extends StatelessWidget { Expanded( child: LSButtonSlim( text: 'Exception', - backgroundColor: LSColors.red, - onTap: () async => GlobalDialogs.textPreview(context, 'Exception', log?.exception ?? 'Unavailable', alignLeft: true), + backgroundColor: LunaColours.red, + onTap: () async => LunaDialogs.textPreview(context, 'Exception', log?.exception ?? 'Unavailable', alignLeft: true), margin: EdgeInsets.only(right: 6.0), ), ), Expanded( child: LSButtonSlim( text: 'Stack Trace', - backgroundColor: LSColors.blue, - onTap: () async => GlobalDialogs.textPreview(context, 'Stack Trace', log?.stacktrace ?? 'Unavailable', alignLeft: true), + backgroundColor: LunaColours.blue, + onTap: () async => LunaDialogs.textPreview(context, 'Stack Trace', log?.stacktrace ?? 'Unavailable', alignLeft: true), margin: EdgeInsets.only(left: 6.0), ), ), diff --git a/lib/modules/settings/modules/modules/route.dart b/lib/modules/settings/modules/modules/route.dart index 6de91d40..9fd8e024 100644 --- a/lib/modules/settings/modules/modules/route.dart +++ b/lib/modules/settings/modules/modules/route.dart @@ -3,25 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules.dart'; -class SettingsModulesRoute extends StatefulWidget { +class SettingsModulesRouter { static const ROUTE_NAME = '/settings/modules'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsModulesRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsModulesRoute()), transitionType: LunaRouter.transitionType, ); - SettingsModulesRoute({ - Key key, - }): super(key: key); - - @override - State createState() => _State(); + SettingsModulesRouter._(); } -class _State extends State with AutomaticKeepAliveClientMixin { +class _SettingsModulesRoute extends StatefulWidget { + @override + State<_SettingsModulesRoute> createState() => _State(); +} + +class _State extends State<_SettingsModulesRoute> with AutomaticKeepAliveClientMixin { final GlobalKey _scaffoldKey = GlobalKey(); @override @@ -37,7 +43,9 @@ class _State extends State with AutomaticKeepAliveClientMi ); } - Widget get _appBar => LSAppBar( + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', title: 'Modules', actions: [ SettingsModulesEnabledProfileButton(), @@ -57,26 +65,26 @@ class _State extends State with AutomaticKeepAliveClientMi ); List get _general => [ - _tileFromModuleMap(SearchConstants.MODULE_MAP, () async => SettingsRouter.router.navigateTo(context, SettingsModulesSearchRoute.ROUTE_NAME)), - _tileFromModuleMap(WakeOnLANConstants.MODULE_MAP, () async => SettingsRouter.router.navigateTo(context, SettingsModulesWakeOnLANRoute.ROUTE_NAME)), + _tileFromModuleMap(SearchConstants.MODULE_MAP, () async => SettingsModulesSearchRouter.navigateTo(context)), + _tileFromModuleMap(WakeOnLANConstants.MODULE_MAP, () async => SettingsModulesWakeOnLANRouter.navigateTo(context)), ]; List get _automation => [ - _tileFromModuleMap(LidarrConstants.MODULE_MAP, () async => SettingsRouter.router.navigateTo(context, SettingsModulesLidarrRoute.ROUTE_NAME)), - _tileFromModuleMap(RadarrConstants.MODULE_MAP, () async => SettingsRouter.router.navigateTo(context, SettingsModulesRadarrRoute.ROUTE_NAME)), - _tileFromModuleMap(SonarrConstants.MODULE_MAP, () async => SettingsRouter.router.navigateTo(context, SettingsModulesSonarrRoute.ROUTE_NAME)), + _tileFromModuleMap(LidarrConstants.MODULE_MAP, () async => SettingsModulesLidarrRouter.navigateTo(context)), + _tileFromModuleMap(RadarrConstants.MODULE_MAP, () async => SettingsModulesRadarrRouter.navigateTo(context)), + _tileFromModuleMap(SonarrConstants.MODULE_MAP, () async => SettingsModulesSonarrRouter.navigateTo(context)), ]; List get _clients => [ - _tileFromModuleMap(NZBGetConstants.MODULE_MAP, () async => SettingsRouter.router.navigateTo(context, SettingsModulesNZBGetRoute.ROUTE_NAME)), - _tileFromModuleMap(SABnzbdConstants.MODULE_MAP, () async => SettingsRouter.router.navigateTo(context, SettingsModulesSABnzbdRoute.ROUTE_NAME)), + _tileFromModuleMap(NZBGetConstants.MODULE_MAP, () async => SettingsModulesNZBGetRouter.navigateTo(context)), + _tileFromModuleMap(SABnzbdConstants.MODULE_MAP, () async => SettingsModulesSABnzbdRouter.navigateTo(context)), ]; List get _monitoring => [ - _tileFromModuleMap(TautulliConstants.MODULE_MAP, () async => SettingsRouter.router.navigateTo(context, SettingsModulesTautulliRoute.ROUTE_NAME)), + _tileFromModuleMap(TautulliConstants.MODULE_MAP, () async => SettingsModulesTautulliRouter.navigateTo(context)), ]; - Widget _tileFromModuleMap(ModuleMap map, Function onTap) => LSCardTile( + Widget _tileFromModuleMap(LunaModuleMap map, Function onTap) => LSCardTile( title: LSTitle(text: map.name), subtitle: LSSubtitle(text: map.settingsDescription), trailing: LSIconButton(icon: map.icon), diff --git a/lib/modules/settings/modules/modules/widgets/enabled_profile.dart b/lib/modules/settings/modules/modules/widgets/enabled_profile.dart index a91a62fc..1a73fb68 100644 --- a/lib/modules/settings/modules/modules/widgets/enabled_profile.dart +++ b/lib/modules/settings/modules/modules/widgets/enabled_profile.dart @@ -14,17 +14,7 @@ class SettingsModulesEnabledProfileButton extends StatelessWidget { context, Database.profilesBox.keys.map((x) => x as String).toList()..sort((a,b) => a.toLowerCase().compareTo(b.toLowerCase())), ); - if(values[0]) { - if(values[1] != LunaSeaDatabaseValue.ENABLED_PROFILE.data) { - LunaSeaDatabaseValue.ENABLED_PROFILE.put(values[1]); - Providers.reset(context); - } - LSSnackBar( - context: context, - title: 'Changed Profile', - message: 'Using profile "${values[1]}"', - type: SNACKBAR_TYPE.info, - ); - } + if(values[0] && values[1] != LunaSeaDatabaseValue.ENABLED_PROFILE.data) + LunaProfile.changeProfile(context, values[1]); } } diff --git a/lib/modules/settings/modules/modules_lidarr/route.dart b/lib/modules/settings/modules/modules_lidarr/route.dart index 92ae1c5e..d22f18f7 100644 --- a/lib/modules/settings/modules/modules_lidarr/route.dart +++ b/lib/modules/settings/modules/modules_lidarr/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsModulesLidarrRoute extends StatefulWidget { +class SettingsModulesLidarrRouter { static const ROUTE_NAME = '/settings/modules/lidarr'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsModulesLidarrRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsModulesLidarrRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsModulesLidarrRouter._(); } -class _State extends State { +class _SettingsModulesLidarrRoute extends StatefulWidget { + @override + State<_SettingsModulesLidarrRoute> createState() => _State(); +} + +class _State extends State<_SettingsModulesLidarrRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override @@ -27,12 +37,14 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar( + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', title: 'Lidarr', actions: [ LSIconButton( icon: Icons.brush, - onPressed: () async => SettingsRouter.router.navigateTo(context, SettingsCustomizationLidarrRoute.ROUTE_NAME), + onPressed: () async => SettingsCustomizationLidarrRouter.navigateTo(context), ), ] ); @@ -41,30 +53,16 @@ class _State extends State { valueListenable: Database.profilesBox.listenable(), builder: (context, box, _) => LSListView( children: [ - ..._mandatory, - LSDivider(), + ..._configuration, SettingsModulesLidarrTestConnectionTile(), - ..._advanced, ], ), ); - List get _mandatory => [ - LSHeader( - text: 'Mandatory', - subtitle: 'Configuration that is required for functionality', - ), + List get _configuration => [ SettingsModulesLidarrEnabledTile(), SettingsModulesLidarrHostTile(), SettingsModulesLidarrAPIKeyTile(), - ]; - - List get _advanced => [ - LSHeader( - text: 'Advanced', - subtitle: 'Options for non-standard networking configurations', - ), SettingsModulesLidarrCustomHeadersTile(), - SettingsModulesLidarrStrictTLSTile(), ]; } diff --git a/lib/modules/settings/modules/modules_lidarr/widgets.dart b/lib/modules/settings/modules/modules_lidarr/widgets.dart index 84d587e2..9d21dbdd 100644 --- a/lib/modules/settings/modules/modules_lidarr/widgets.dart +++ b/lib/modules/settings/modules/modules_lidarr/widgets.dart @@ -2,5 +2,4 @@ export 'widgets/apikey_tile.dart'; export 'widgets/custom_headers_tile.dart'; export 'widgets/enabled_tile.dart'; export 'widgets/host_tile.dart'; -export 'widgets/strict_tls_tile.dart'; export 'widgets/test_connection_tile.dart'; diff --git a/lib/modules/settings/modules/modules_lidarr/widgets/apikey_tile.dart b/lib/modules/settings/modules/modules_lidarr/widgets/apikey_tile.dart index 0b56bee8..fbe6707c 100644 --- a/lib/modules/settings/modules/modules_lidarr/widgets/apikey_tile.dart +++ b/lib/modules/settings/modules/modules_lidarr/widgets/apikey_tile.dart @@ -15,14 +15,14 @@ class SettingsModulesLidarrAPIKeyTile extends StatelessWidget { ); Future _changeKey(BuildContext context) async { - List _values = await GlobalDialogs.editText( + List _values = await LunaDialogs.editText( context, 'Lidarr API Key', prefill: Database.currentProfileObject.lidarrKey ?? '', ); if(_values[0]) { Database.currentProfileObject.lidarrKey = _values[1]; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); } } -} \ No newline at end of file +} diff --git a/lib/modules/settings/modules/modules_lidarr/widgets/custom_headers_tile.dart b/lib/modules/settings/modules/modules_lidarr/widgets/custom_headers_tile.dart index 082afa80..db18f5cf 100644 --- a/lib/modules/settings/modules/modules_lidarr/widgets/custom_headers_tile.dart +++ b/lib/modules/settings/modules/modules_lidarr/widgets/custom_headers_tile.dart @@ -8,6 +8,6 @@ class SettingsModulesLidarrCustomHeadersTile extends StatelessWidget { title: LSTitle(text: 'Custom Headers'), subtitle: LSSubtitle(text: 'Add Custom Headers to Requests'), trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsModulesLidarrHeadersRoute.ROUTE_NAME), + onTap: () async => SettingsModulesLidarrHeadersRouter.navigateTo(context), ); } diff --git a/lib/modules/settings/modules/modules_lidarr/widgets/enabled_tile.dart b/lib/modules/settings/modules/modules_lidarr/widgets/enabled_tile.dart index c80ca87e..b01bd0ec 100644 --- a/lib/modules/settings/modules/modules_lidarr/widgets/enabled_tile.dart +++ b/lib/modules/settings/modules/modules_lidarr/widgets/enabled_tile.dart @@ -9,7 +9,7 @@ class SettingsModulesLidarrEnabledTile extends StatelessWidget { value: Database.currentProfileObject.lidarrEnabled ?? false, onChanged: (value) { Database.currentProfileObject.lidarrEnabled = value; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); }, ), ); diff --git a/lib/modules/settings/modules/modules_lidarr/widgets/host_tile.dart b/lib/modules/settings/modules/modules_lidarr/widgets/host_tile.dart index b2ee6f80..6590652a 100644 --- a/lib/modules/settings/modules/modules_lidarr/widgets/host_tile.dart +++ b/lib/modules/settings/modules/modules_lidarr/widgets/host_tile.dart @@ -23,7 +23,7 @@ class SettingsModulesLidarrHostTile extends StatelessWidget { ); if(_values[0]) { Database.currentProfileObject.lidarrHost = _values[1]; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); } } } \ No newline at end of file diff --git a/lib/modules/settings/modules/modules_lidarr/widgets/strict_tls_tile.dart b/lib/modules/settings/modules/modules_lidarr/widgets/strict_tls_tile.dart deleted file mode 100644 index 53f77a9e..00000000 --- a/lib/modules/settings/modules/modules_lidarr/widgets/strict_tls_tile.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/settings.dart'; - -class SettingsModulesLidarrStrictTLSTile extends StatelessWidget { - @override - Widget build(BuildContext context) => LSCardTile( - title: LSTitle(text: 'Strict SSL/TLS Validation'), - subtitle: LSSubtitle(text: 'For Invalid Certificates'), - trailing: Switch( - value: Database.currentProfileObject.lidarrStrictTLS ?? true, - onChanged: (value) async => _onChanged(context, value), - ), - ); - - Future _onChanged(BuildContext context, bool value) async { - if(value) { - Database.currentProfileObject.lidarrStrictTLS = value; - Database.currentProfileObject.save(context: context); - } else { - List _values = await SettingsDialogs.toggleStrictTLS(context); - if(_values[0]) { - Database.currentProfileObject.lidarrStrictTLS = value; - Database.currentProfileObject.save(context: context); - } - } - } -} \ No newline at end of file diff --git a/lib/modules/settings/modules/modules_lidarr_headers/route.dart b/lib/modules/settings/modules/modules_lidarr_headers/route.dart index c9d91ec0..7708d5da 100644 --- a/lib/modules/settings/modules/modules_lidarr_headers/route.dart +++ b/lib/modules/settings/modules/modules_lidarr_headers/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsModulesLidarrHeadersRoute extends StatefulWidget { +class SettingsModulesLidarrHeadersRouter { static const ROUTE_NAME = '/settings/modules/lidarr/headers'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsModulesLidarrHeadersRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsModulesLidarrHeadersRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsModulesLidarrHeadersRouter._(); } -class _State extends State { +class _SettingsModulesLidarrHeadersRoute extends StatefulWidget { + @override + State<_SettingsModulesLidarrHeadersRoute> createState() => _State(); +} + +class _State extends State<_SettingsModulesLidarrHeadersRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override @@ -27,7 +37,11 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Custom Headers'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', + title: 'Custom Headers', + ); Widget get _body => ValueListenableBuilder( valueListenable: Database.profilesBox.listenable(), @@ -39,7 +53,6 @@ class _State extends State { List get _headers => [ if((Database.currentProfileObject.lidarrHeaders ?? {}).isEmpty) _noHeaders, ..._list, - LSDivider(), SettingsModulesLidarrHeadersAddHeaderTile(), ]; diff --git a/lib/modules/settings/modules/modules_lidarr_headers/widgets/add_header_tile.dart b/lib/modules/settings/modules/modules_lidarr_headers/widgets/add_header_tile.dart index 3daa57c3..2609f213 100644 --- a/lib/modules/settings/modules/modules_lidarr_headers/widgets/add_header_tile.dart +++ b/lib/modules/settings/modules/modules_lidarr_headers/widgets/add_header_tile.dart @@ -20,7 +20,7 @@ class SettingsModulesLidarrHeadersAddHeaderTile extends StatelessWidget { _showCustomPrompt(context); break; default: - Logger.warning( + LunaLogger.warning( 'SettingsModulesLidarrHeadersAddHeaderTile', '_addPrompt', 'Unknown case: ${results[1]}', @@ -36,7 +36,7 @@ class SettingsModulesLidarrHeadersAddHeaderTile extends StatelessWidget { String _auth = base64.encode(utf8.encode('${results[1]}:${results[2]}')); _headers.addAll({'Authorization': 'Basic $_auth'}); Database.currentProfileObject.lidarrHeaders = _headers; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); } } @@ -46,7 +46,7 @@ class SettingsModulesLidarrHeadersAddHeaderTile extends StatelessWidget { Map _headers = (Database.currentProfileObject.lidarrHeaders ?? {}).cast(); _headers.addAll({results[1]: results[2]}); Database.currentProfileObject.lidarrHeaders = _headers; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); } } } \ No newline at end of file diff --git a/lib/modules/settings/modules/modules_lidarr_headers/widgets/header_tile.dart b/lib/modules/settings/modules/modules_lidarr_headers/widgets/header_tile.dart index 0378a94f..bbe25605 100644 --- a/lib/modules/settings/modules/modules_lidarr_headers/widgets/header_tile.dart +++ b/lib/modules/settings/modules/modules_lidarr_headers/widgets/header_tile.dart @@ -17,7 +17,7 @@ class SettingsModulesLidarrHeadersHeaderTile extends StatelessWidget { subtitle: LSSubtitle(text: headerValue), trailing: LSIconButton( icon: Icons.delete, - color: LSColors.red, + color: LunaColours.red, onPressed: () async => _deleteHeader(context), ), ); @@ -28,7 +28,7 @@ class SettingsModulesLidarrHeadersHeaderTile extends StatelessWidget { Map _headers = (Database.currentProfileObject.lidarrHeaders ?? {}).cast(); _headers.remove(headerKey); Database.currentProfileObject.lidarrHeaders = _headers; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); LSSnackBar( context: context, message: headerKey, diff --git a/lib/modules/settings/modules/modules_nzbget/route.dart b/lib/modules/settings/modules/modules_nzbget/route.dart index 0fab270c..27e998f7 100644 --- a/lib/modules/settings/modules/modules_nzbget/route.dart +++ b/lib/modules/settings/modules/modules_nzbget/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsModulesNZBGetRoute extends StatefulWidget { +class SettingsModulesNZBGetRouter { static const ROUTE_NAME = '/settings/modules/nzbget'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsModulesNZBGetRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsModulesNZBGetRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsModulesNZBGetRouter._(); } -class _State extends State { +class _SettingsModulesNZBGetRoute extends StatefulWidget { + @override + State<_SettingsModulesNZBGetRoute> createState() => _State(); +} + +class _State extends State<_SettingsModulesNZBGetRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override @@ -27,12 +37,14 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar( + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', title: 'NZBGet', actions: [ LSIconButton( icon: Icons.brush, - onPressed: () async => SettingsRouter.router.navigateTo(context, SettingsCustomizationNZBGetRoute.ROUTE_NAME), + onPressed: () async => SettingsCustomizationNZBGetRouter.navigateTo(context), ), ] ); @@ -41,32 +53,17 @@ class _State extends State { valueListenable: Database.profilesBox.listenable(), builder: (context, box, _) => LSListView( children: [ - ..._mandatory, - LSDivider(), + ..._configuration, SettingsModulesNZBGetTestConnectionTile(), - ..._advanced, ], ), ); - List get _mandatory => [ - LSHeader( - text: 'Mandatory', - subtitle: 'Configuration that is required for functionality', - ), + List get _configuration => [ SettingsModulesNZBGetEnabledTile(), SettingsModulesNZBGetHostTile(), SettingsModulesNZBGetUsernameTile(), SettingsModulesNZBGetPasswordTile(), - - ]; - - List get _advanced => [ - LSHeader( - text: 'Advanced', - subtitle: 'Options for non-standard networking configurations', - ), SettingsModulesNZBGetCustomHeadersTile(), - SettingsModulesNZBGetStrictTLSTile(), ]; } diff --git a/lib/modules/settings/modules/modules_nzbget/widgets.dart b/lib/modules/settings/modules/modules_nzbget/widgets.dart index 206523ed..8446093a 100644 --- a/lib/modules/settings/modules/modules_nzbget/widgets.dart +++ b/lib/modules/settings/modules/modules_nzbget/widgets.dart @@ -3,5 +3,4 @@ export 'widgets/custom_headers_tile.dart'; export 'widgets/enabled_tile.dart'; export 'widgets/host_tile.dart'; export 'widgets/password_tile.dart'; -export 'widgets/strict_tls_tile.dart'; export 'widgets/test_connection_tile.dart'; diff --git a/lib/modules/settings/modules/modules_nzbget/widgets/custom_headers_tile.dart b/lib/modules/settings/modules/modules_nzbget/widgets/custom_headers_tile.dart index 563147d8..32da96ba 100644 --- a/lib/modules/settings/modules/modules_nzbget/widgets/custom_headers_tile.dart +++ b/lib/modules/settings/modules/modules_nzbget/widgets/custom_headers_tile.dart @@ -8,6 +8,6 @@ class SettingsModulesNZBGetCustomHeadersTile extends StatelessWidget { title: LSTitle(text: 'Custom Headers'), subtitle: LSSubtitle(text: 'Add Custom Headers to Requests'), trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsModulesNZBGetHeadersRoute.ROUTE_NAME), + onTap: () async => SettingsModulesNZBGetHeadersRouter.navigateTo(context), ); } diff --git a/lib/modules/settings/modules/modules_nzbget/widgets/enabled_tile.dart b/lib/modules/settings/modules/modules_nzbget/widgets/enabled_tile.dart index 5c2915d8..1092332a 100644 --- a/lib/modules/settings/modules/modules_nzbget/widgets/enabled_tile.dart +++ b/lib/modules/settings/modules/modules_nzbget/widgets/enabled_tile.dart @@ -9,7 +9,7 @@ class SettingsModulesNZBGetEnabledTile extends StatelessWidget { value: Database.currentProfileObject.nzbgetEnabled ?? false, onChanged: (value) { Database.currentProfileObject.nzbgetEnabled = value; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); }, ), ); diff --git a/lib/modules/settings/modules/modules_nzbget/widgets/host_tile.dart b/lib/modules/settings/modules/modules_nzbget/widgets/host_tile.dart index 6e819238..d79ec8ac 100644 --- a/lib/modules/settings/modules/modules_nzbget/widgets/host_tile.dart +++ b/lib/modules/settings/modules/modules_nzbget/widgets/host_tile.dart @@ -23,7 +23,7 @@ class SettingsModulesNZBGetHostTile extends StatelessWidget { ); if(_values[0]) { Database.currentProfileObject.nzbgetHost = _values[1]; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); } } } \ No newline at end of file diff --git a/lib/modules/settings/modules/modules_nzbget/widgets/password_tile.dart b/lib/modules/settings/modules/modules_nzbget/widgets/password_tile.dart index 6e1bc321..a321fc05 100644 --- a/lib/modules/settings/modules/modules_nzbget/widgets/password_tile.dart +++ b/lib/modules/settings/modules/modules_nzbget/widgets/password_tile.dart @@ -15,14 +15,14 @@ class SettingsModulesNZBGetPasswordTile extends StatelessWidget { ); Future _changePassword(BuildContext context) async { - List _values = await GlobalDialogs.editText( + List _values = await LunaDialogs.editText( context, 'NZBGet Password', prefill: Database.currentProfileObject.nzbgetPass ?? '', ); if(_values[0]) { Database.currentProfileObject.nzbgetPass = _values[1]; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); } } } \ No newline at end of file diff --git a/lib/modules/settings/modules/modules_nzbget/widgets/strict_tls_tile.dart b/lib/modules/settings/modules/modules_nzbget/widgets/strict_tls_tile.dart deleted file mode 100644 index 6cce218e..00000000 --- a/lib/modules/settings/modules/modules_nzbget/widgets/strict_tls_tile.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/settings.dart'; - -class SettingsModulesNZBGetStrictTLSTile extends StatelessWidget { - @override - Widget build(BuildContext context) => LSCardTile( - title: LSTitle(text: 'Strict SSL/TLS Validation'), - subtitle: LSSubtitle(text: 'For Invalid Certificates'), - trailing: Switch( - value: Database.currentProfileObject.nzbgetStrictTLS ?? true, - onChanged: (value) async => _onChanged(context, value), - ), - ); - - Future _onChanged(BuildContext context, bool value) async { - if(value) { - Database.currentProfileObject.nzbgetStrictTLS = value; - Database.currentProfileObject.save(context: context); - } else { - List _values = await SettingsDialogs.toggleStrictTLS(context); - if(_values[0]) { - Database.currentProfileObject.nzbgetStrictTLS = value; - Database.currentProfileObject.save(context: context); - } - } - } -} \ No newline at end of file diff --git a/lib/modules/settings/modules/modules_nzbget/widgets/username_tile.dart b/lib/modules/settings/modules/modules_nzbget/widgets/username_tile.dart index 291b0640..d924baee 100644 --- a/lib/modules/settings/modules/modules_nzbget/widgets/username_tile.dart +++ b/lib/modules/settings/modules/modules_nzbget/widgets/username_tile.dart @@ -15,14 +15,14 @@ class SettingsModulesNZBGetUsernameTile extends StatelessWidget { ); Future _changeUsername(BuildContext context) async { - List _values = await GlobalDialogs.editText( + List _values = await LunaDialogs.editText( context, 'NZBGet Username', prefill: Database.currentProfileObject.nzbgetUser ?? '', ); if(_values[0]) { Database.currentProfileObject.nzbgetUser = _values[1]; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); } } } diff --git a/lib/modules/settings/modules/modules_nzbget_headers/route.dart b/lib/modules/settings/modules/modules_nzbget_headers/route.dart index 0b4f796a..e17d616c 100644 --- a/lib/modules/settings/modules/modules_nzbget_headers/route.dart +++ b/lib/modules/settings/modules/modules_nzbget_headers/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsModulesNZBGetHeadersRoute extends StatefulWidget { +class SettingsModulesNZBGetHeadersRouter { static const ROUTE_NAME = '/settings/modules/nzbget/headers'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsModulesNZBGetHeadersRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsModulesNZBGetHeadersRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsModulesNZBGetHeadersRouter._(); } -class _State extends State { +class _SettingsModulesNZBGetHeadersRoute extends StatefulWidget { + @override + State<_SettingsModulesNZBGetHeadersRoute> createState() => _State(); +} + +class _State extends State<_SettingsModulesNZBGetHeadersRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override @@ -27,7 +37,11 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Custom Headers'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', + title: 'Custom Headers', + ); Widget get _body => ValueListenableBuilder( valueListenable: Database.profilesBox.listenable(), @@ -39,7 +53,6 @@ class _State extends State { List get _headers => [ if((Database.currentProfileObject.nzbgetHeaders ?? {}).isEmpty) _noHeaders, ..._list, - LSDivider(), SettingsModulesNZBGetHeadersAddHeaderTile(), ]; diff --git a/lib/modules/settings/modules/modules_nzbget_headers/widgets/add_header_tile.dart b/lib/modules/settings/modules/modules_nzbget_headers/widgets/add_header_tile.dart index 05b27959..f791a52c 100644 --- a/lib/modules/settings/modules/modules_nzbget_headers/widgets/add_header_tile.dart +++ b/lib/modules/settings/modules/modules_nzbget_headers/widgets/add_header_tile.dart @@ -20,7 +20,7 @@ class SettingsModulesNZBGetHeadersAddHeaderTile extends StatelessWidget { _showCustomPrompt(context); break; default: - Logger.warning( + LunaLogger.warning( 'SettingsModulesNZBGetHeadersAddHeaderTile', '_addPrompt', 'Unknown case: ${results[1]}', @@ -36,7 +36,7 @@ class SettingsModulesNZBGetHeadersAddHeaderTile extends StatelessWidget { String _auth = base64.encode(utf8.encode('${results[1]}:${results[2]}')); _headers.addAll({'Authorization': 'Basic $_auth'}); Database.currentProfileObject.nzbgetHeaders = _headers; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); } } @@ -46,7 +46,7 @@ class SettingsModulesNZBGetHeadersAddHeaderTile extends StatelessWidget { Map _headers = (Database.currentProfileObject.nzbgetHeaders ?? {}).cast(); _headers.addAll({results[1]: results[2]}); Database.currentProfileObject.nzbgetHeaders = _headers; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); } } } \ No newline at end of file diff --git a/lib/modules/settings/modules/modules_nzbget_headers/widgets/header_tile.dart b/lib/modules/settings/modules/modules_nzbget_headers/widgets/header_tile.dart index 3c67d788..b97956b0 100644 --- a/lib/modules/settings/modules/modules_nzbget_headers/widgets/header_tile.dart +++ b/lib/modules/settings/modules/modules_nzbget_headers/widgets/header_tile.dart @@ -17,7 +17,7 @@ class SettingsModulesNZBGetHeadersHeaderTile extends StatelessWidget { subtitle: LSSubtitle(text: headerValue), trailing: LSIconButton( icon: Icons.delete, - color: LSColors.red, + color: LunaColours.red, onPressed: () async => _deleteHeader(context), ), ); @@ -28,7 +28,7 @@ class SettingsModulesNZBGetHeadersHeaderTile extends StatelessWidget { Map _headers = (Database.currentProfileObject.nzbgetHeaders ?? {}).cast(); _headers.remove(headerKey); Database.currentProfileObject.nzbgetHeaders = _headers; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); LSSnackBar( context: context, message: headerKey, diff --git a/lib/modules/settings/modules/modules_radarr/route.dart b/lib/modules/settings/modules/modules_radarr/route.dart index 4d1bbffc..42015850 100644 --- a/lib/modules/settings/modules/modules_radarr/route.dart +++ b/lib/modules/settings/modules/modules_radarr/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsModulesRadarrRoute extends StatefulWidget { +class SettingsModulesRadarrRouter { static const ROUTE_NAME = '/settings/modules/radarr'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsModulesRadarrRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsModulesRadarrRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsModulesRadarrRouter._(); } -class _State extends State { +class _SettingsModulesRadarrRoute extends StatefulWidget { + @override + State<_SettingsModulesRadarrRoute> createState() => _State(); +} + +class _State extends State<_SettingsModulesRadarrRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override @@ -27,12 +37,14 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar( + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', title: 'Radarr', actions: [ LSIconButton( icon: Icons.brush, - onPressed: () async => SettingsRouter.router.navigateTo(context, SettingsCustomizationRadarrRoute.ROUTE_NAME), + onPressed: () async => SettingsCustomizationRadarrRouter.navigateTo(context), ), ] ); @@ -41,30 +53,16 @@ class _State extends State { valueListenable: Database.profilesBox.listenable(), builder: (context, box, _) => LSListView( children: [ - ..._mandatory, - LSDivider(), + ..._configuration, SettingsModulesRadarrTestConnectionTile(), - ..._advanced, ], ), ); - List get _mandatory => [ - LSHeader( - text: 'Mandatory', - subtitle: 'Configuration that is required for functionality', - ), + List get _configuration => [ SettingsModulesRadarrEnabledTile(), SettingsModulesRadarrHostTile(), SettingsModulesRadarrAPIKeyTile(), - ]; - - List get _advanced => [ - LSHeader( - text: 'Advanced', - subtitle: 'Options for non-standard networking configurations', - ), SettingsModulesRadarrCustomHeadersTile(), - SettingsModulesRadarrStrictTLSTile(), ]; } diff --git a/lib/modules/settings/modules/modules_radarr/widgets.dart b/lib/modules/settings/modules/modules_radarr/widgets.dart index 84d587e2..9d21dbdd 100644 --- a/lib/modules/settings/modules/modules_radarr/widgets.dart +++ b/lib/modules/settings/modules/modules_radarr/widgets.dart @@ -2,5 +2,4 @@ export 'widgets/apikey_tile.dart'; export 'widgets/custom_headers_tile.dart'; export 'widgets/enabled_tile.dart'; export 'widgets/host_tile.dart'; -export 'widgets/strict_tls_tile.dart'; export 'widgets/test_connection_tile.dart'; diff --git a/lib/modules/settings/modules/modules_radarr/widgets/apikey_tile.dart b/lib/modules/settings/modules/modules_radarr/widgets/apikey_tile.dart index b199d448..fdc297b6 100644 --- a/lib/modules/settings/modules/modules_radarr/widgets/apikey_tile.dart +++ b/lib/modules/settings/modules/modules_radarr/widgets/apikey_tile.dart @@ -15,14 +15,14 @@ class SettingsModulesRadarrAPIKeyTile extends StatelessWidget { ); Future _changeKey(BuildContext context) async { - List _values = await GlobalDialogs.editText( + List _values = await LunaDialogs.editText( context, 'Radarr API Key', prefill: Database.currentProfileObject.radarrKey ?? '', ); if(_values[0]) { Database.currentProfileObject.radarrKey = _values[1]; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); } } } \ No newline at end of file diff --git a/lib/modules/settings/modules/modules_radarr/widgets/custom_headers_tile.dart b/lib/modules/settings/modules/modules_radarr/widgets/custom_headers_tile.dart index ba1ce54d..cf46af3f 100644 --- a/lib/modules/settings/modules/modules_radarr/widgets/custom_headers_tile.dart +++ b/lib/modules/settings/modules/modules_radarr/widgets/custom_headers_tile.dart @@ -8,6 +8,6 @@ class SettingsModulesRadarrCustomHeadersTile extends StatelessWidget { title: LSTitle(text: 'Custom Headers'), subtitle: LSSubtitle(text: 'Add Custom Headers to Requests'), trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsModulesRadarrHeadersRoute.ROUTE_NAME), + onTap: () async => SettingsModulesRadarrHeadersRouter.navigateTo(context), ); } diff --git a/lib/modules/settings/modules/modules_radarr/widgets/enabled_tile.dart b/lib/modules/settings/modules/modules_radarr/widgets/enabled_tile.dart index 21ff2d91..0a3ca460 100644 --- a/lib/modules/settings/modules/modules_radarr/widgets/enabled_tile.dart +++ b/lib/modules/settings/modules/modules_radarr/widgets/enabled_tile.dart @@ -9,7 +9,7 @@ class SettingsModulesRadarrEnabledTile extends StatelessWidget { value: Database.currentProfileObject.radarrEnabled ?? false, onChanged: (value) { Database.currentProfileObject.radarrEnabled = value; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); }, ), ); diff --git a/lib/modules/settings/modules/modules_radarr/widgets/host_tile.dart b/lib/modules/settings/modules/modules_radarr/widgets/host_tile.dart index 751737a6..79c8b7c6 100644 --- a/lib/modules/settings/modules/modules_radarr/widgets/host_tile.dart +++ b/lib/modules/settings/modules/modules_radarr/widgets/host_tile.dart @@ -23,7 +23,7 @@ class SettingsModulesRadarrHostTile extends StatelessWidget { ); if(_values[0]) { Database.currentProfileObject.radarrHost = _values[1]; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); } } } \ No newline at end of file diff --git a/lib/modules/settings/modules/modules_radarr/widgets/strict_tls_tile.dart b/lib/modules/settings/modules/modules_radarr/widgets/strict_tls_tile.dart deleted file mode 100644 index ebbeff92..00000000 --- a/lib/modules/settings/modules/modules_radarr/widgets/strict_tls_tile.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/settings.dart'; - -class SettingsModulesRadarrStrictTLSTile extends StatelessWidget { - @override - Widget build(BuildContext context) => LSCardTile( - title: LSTitle(text: 'Strict SSL/TLS Validation'), - subtitle: LSSubtitle(text: 'For Invalid Certificates'), - trailing: Switch( - value: Database.currentProfileObject.radarrStrictTLS ?? true, - onChanged: (value) async => _onChanged(context, value), - ), - ); - - Future _onChanged(BuildContext context, bool value) async { - if(value) { - Database.currentProfileObject.radarrStrictTLS = value; - Database.currentProfileObject.save(context: context); - } else { - List _values = await SettingsDialogs.toggleStrictTLS(context); - if(_values[0]) { - Database.currentProfileObject.radarrStrictTLS = value; - Database.currentProfileObject.save(context: context); - } - } - } -} \ No newline at end of file diff --git a/lib/modules/settings/modules/modules_radarr_headers/route.dart b/lib/modules/settings/modules/modules_radarr_headers/route.dart index 7540057d..a6b45352 100644 --- a/lib/modules/settings/modules/modules_radarr_headers/route.dart +++ b/lib/modules/settings/modules/modules_radarr_headers/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsModulesRadarrHeadersRoute extends StatefulWidget { +class SettingsModulesRadarrHeadersRouter { static const ROUTE_NAME = '/settings/modules/radarr/headers'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsModulesRadarrHeadersRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsModulesRadarrHeadersRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsModulesRadarrHeadersRouter._(); } -class _State extends State { +class _SettingsModulesRadarrHeadersRoute extends StatefulWidget { + @override + State<_SettingsModulesRadarrHeadersRoute> createState() => _State(); +} + +class _State extends State<_SettingsModulesRadarrHeadersRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override @@ -27,7 +37,11 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Custom Headers'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', + title: 'Custom Headers', + ); Widget get _body => ValueListenableBuilder( valueListenable: Database.profilesBox.listenable(), @@ -39,7 +53,6 @@ class _State extends State { List get _headers => [ if((Database.currentProfileObject.radarrHeaders ?? {}).isEmpty) _noHeaders, ..._list, - LSDivider(), SettingsModulesRadarrHeadersAddHeaderTile(), ]; diff --git a/lib/modules/settings/modules/modules_radarr_headers/widgets/add_header_tile.dart b/lib/modules/settings/modules/modules_radarr_headers/widgets/add_header_tile.dart index 4f6ceeac..53e0d57b 100644 --- a/lib/modules/settings/modules/modules_radarr_headers/widgets/add_header_tile.dart +++ b/lib/modules/settings/modules/modules_radarr_headers/widgets/add_header_tile.dart @@ -20,7 +20,7 @@ class SettingsModulesRadarrHeadersAddHeaderTile extends StatelessWidget { _showCustomPrompt(context); break; default: - Logger.warning( + LunaLogger.warning( 'SettingsModulesRadarrHeadersAddHeaderTile', '_addPrompt', 'Unknown case: ${results[1]}', @@ -36,7 +36,7 @@ class SettingsModulesRadarrHeadersAddHeaderTile extends StatelessWidget { String _auth = base64.encode(utf8.encode('${results[1]}:${results[2]}')); _headers.addAll({'Authorization': 'Basic $_auth'}); Database.currentProfileObject.radarrHeaders = _headers; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); } } @@ -46,7 +46,7 @@ class SettingsModulesRadarrHeadersAddHeaderTile extends StatelessWidget { Map _headers = (Database.currentProfileObject.radarrHeaders ?? {}).cast(); _headers.addAll({results[1]: results[2]}); Database.currentProfileObject.radarrHeaders = _headers; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); } } } \ No newline at end of file diff --git a/lib/modules/settings/modules/modules_radarr_headers/widgets/header_tile.dart b/lib/modules/settings/modules/modules_radarr_headers/widgets/header_tile.dart index ba44865e..ee00605c 100644 --- a/lib/modules/settings/modules/modules_radarr_headers/widgets/header_tile.dart +++ b/lib/modules/settings/modules/modules_radarr_headers/widgets/header_tile.dart @@ -17,7 +17,7 @@ class SettingsModulesRadarrHeadersHeaderTile extends StatelessWidget { subtitle: LSSubtitle(text: headerValue), trailing: LSIconButton( icon: Icons.delete, - color: LSColors.red, + color: LunaColours.red, onPressed: () async => _deleteHeader(context), ), ); @@ -28,7 +28,7 @@ class SettingsModulesRadarrHeadersHeaderTile extends StatelessWidget { Map _headers = (Database.currentProfileObject.radarrHeaders ?? {}).cast(); _headers.remove(headerKey); Database.currentProfileObject.radarrHeaders = _headers; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); LSSnackBar( context: context, message: headerKey, diff --git a/lib/modules/settings/modules/modules_sabnzbd/route.dart b/lib/modules/settings/modules/modules_sabnzbd/route.dart index 8f341c63..cc505c09 100644 --- a/lib/modules/settings/modules/modules_sabnzbd/route.dart +++ b/lib/modules/settings/modules/modules_sabnzbd/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsModulesSABnzbdRoute extends StatefulWidget { +class SettingsModulesSABnzbdRouter { static const ROUTE_NAME = '/settings/modules/sabnzbd'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsModulesSABnzbdRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsModulesSABnzbdRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsModulesSABnzbdRouter._(); } -class _State extends State { +class _SettingsModulesSABnzbdRoute extends StatefulWidget { + @override + State<_SettingsModulesSABnzbdRoute> createState() => _State(); +} + +class _State extends State<_SettingsModulesSABnzbdRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override @@ -27,12 +37,14 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar( + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', title: 'SABnzbd', actions: [ LSIconButton( icon: Icons.brush, - onPressed: () async => SettingsRouter.router.navigateTo(context, SettingsCustomizationSABnzbdRoute.ROUTE_NAME), + onPressed: () async => SettingsCustomizationSABnzbdRouter.navigateTo(context), ), ] ); @@ -41,30 +53,16 @@ class _State extends State { valueListenable: Database.profilesBox.listenable(), builder: (context, box, _) => LSListView( children: [ - ..._mandatory, - LSDivider(), + ..._configuration, SettingsModulesSABnzbdTestConnectionTile(), - ..._advanced, ], ), ); - List get _mandatory => [ - LSHeader( - text: 'Mandatory', - subtitle: 'Configuration that is required for functionality', - ), + List get _configuration => [ SettingsModulesSABnzbdEnabledTile(), SettingsModulesSABnzbdHostTile(), SettingsModulesSABnzbdAPIKeyTile(), - ]; - - List get _advanced => [ - LSHeader( - text: 'Advanced', - subtitle: 'Options for non-standard networking configurations', - ), SettingsModulesSABnzbdCustomHeadersTile(), - SettingsModulesSABnzbdStrictTLSTile(), ]; } diff --git a/lib/modules/settings/modules/modules_sabnzbd/widgets.dart b/lib/modules/settings/modules/modules_sabnzbd/widgets.dart index 84d587e2..9d21dbdd 100644 --- a/lib/modules/settings/modules/modules_sabnzbd/widgets.dart +++ b/lib/modules/settings/modules/modules_sabnzbd/widgets.dart @@ -2,5 +2,4 @@ export 'widgets/apikey_tile.dart'; export 'widgets/custom_headers_tile.dart'; export 'widgets/enabled_tile.dart'; export 'widgets/host_tile.dart'; -export 'widgets/strict_tls_tile.dart'; export 'widgets/test_connection_tile.dart'; diff --git a/lib/modules/settings/modules/modules_sabnzbd/widgets/apikey_tile.dart b/lib/modules/settings/modules/modules_sabnzbd/widgets/apikey_tile.dart index c8bec585..55889ac8 100644 --- a/lib/modules/settings/modules/modules_sabnzbd/widgets/apikey_tile.dart +++ b/lib/modules/settings/modules/modules_sabnzbd/widgets/apikey_tile.dart @@ -15,14 +15,14 @@ class SettingsModulesSABnzbdAPIKeyTile extends StatelessWidget { ); Future _changeKey(BuildContext context) async { - List _values = await GlobalDialogs.editText( + List _values = await LunaDialogs.editText( context, 'SABnzbd API Key', prefill: Database.currentProfileObject.sabnzbdKey ?? '', ); if(_values[0]) { Database.currentProfileObject.sabnzbdKey = _values[1]; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); } } } \ No newline at end of file diff --git a/lib/modules/settings/modules/modules_sabnzbd/widgets/custom_headers_tile.dart b/lib/modules/settings/modules/modules_sabnzbd/widgets/custom_headers_tile.dart index f6a6eb88..646f51ac 100644 --- a/lib/modules/settings/modules/modules_sabnzbd/widgets/custom_headers_tile.dart +++ b/lib/modules/settings/modules/modules_sabnzbd/widgets/custom_headers_tile.dart @@ -8,6 +8,6 @@ class SettingsModulesSABnzbdCustomHeadersTile extends StatelessWidget { title: LSTitle(text: 'Custom Headers'), subtitle: LSSubtitle(text: 'Add Custom Headers to Requests'), trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsModulesSABnzbdHeadersRoute.ROUTE_NAME), + onTap: () async => SettingsModulesSABnzbdHeadersRouter.navigateTo(context), ); } diff --git a/lib/modules/settings/modules/modules_sabnzbd/widgets/enabled_tile.dart b/lib/modules/settings/modules/modules_sabnzbd/widgets/enabled_tile.dart index e8185032..966449a4 100644 --- a/lib/modules/settings/modules/modules_sabnzbd/widgets/enabled_tile.dart +++ b/lib/modules/settings/modules/modules_sabnzbd/widgets/enabled_tile.dart @@ -9,7 +9,7 @@ class SettingsModulesSABnzbdEnabledTile extends StatelessWidget { value: Database.currentProfileObject.sabnzbdEnabled ?? false, onChanged: (value) { Database.currentProfileObject.sabnzbdEnabled = value; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); }, ), ); diff --git a/lib/modules/settings/modules/modules_sabnzbd/widgets/host_tile.dart b/lib/modules/settings/modules/modules_sabnzbd/widgets/host_tile.dart index ef45eac2..ac528d89 100644 --- a/lib/modules/settings/modules/modules_sabnzbd/widgets/host_tile.dart +++ b/lib/modules/settings/modules/modules_sabnzbd/widgets/host_tile.dart @@ -23,7 +23,7 @@ class SettingsModulesSABnzbdHostTile extends StatelessWidget { ); if(_values[0]) { Database.currentProfileObject.sabnzbdHost = _values[1]; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); } } } \ No newline at end of file diff --git a/lib/modules/settings/modules/modules_sabnzbd/widgets/strict_tls_tile.dart b/lib/modules/settings/modules/modules_sabnzbd/widgets/strict_tls_tile.dart deleted file mode 100644 index f6e703a7..00000000 --- a/lib/modules/settings/modules/modules_sabnzbd/widgets/strict_tls_tile.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/settings.dart'; - -class SettingsModulesSABnzbdStrictTLSTile extends StatelessWidget { - @override - Widget build(BuildContext context) => LSCardTile( - title: LSTitle(text: 'Strict SSL/TLS Validation'), - subtitle: LSSubtitle(text: 'For Invalid Certificates'), - trailing: Switch( - value: Database.currentProfileObject.sabnzbdStrictTLS ?? true, - onChanged: (value) async => _onChanged(context, value), - ), - ); - - Future _onChanged(BuildContext context, bool value) async { - if(value) { - Database.currentProfileObject.sabnzbdStrictTLS = value; - Database.currentProfileObject.save(context: context); - } else { - List _values = await SettingsDialogs.toggleStrictTLS(context); - if(_values[0]) { - Database.currentProfileObject.sabnzbdStrictTLS = value; - Database.currentProfileObject.save(context: context); - } - } - } -} \ No newline at end of file diff --git a/lib/modules/settings/modules/modules_sabnzbd_headers/route.dart b/lib/modules/settings/modules/modules_sabnzbd_headers/route.dart index e88e3a7d..e30343e8 100644 --- a/lib/modules/settings/modules/modules_sabnzbd_headers/route.dart +++ b/lib/modules/settings/modules/modules_sabnzbd_headers/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsModulesSABnzbdHeadersRoute extends StatefulWidget { +class SettingsModulesSABnzbdHeadersRouter { static const ROUTE_NAME = '/settings/modules/sabnzbd/headers'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsModulesSABnzbdHeadersRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsModulesSABnzbdHeadersRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsModulesSABnzbdHeadersRouter._(); } -class _State extends State { +class _SettingsModulesSABnzbdHeadersRoute extends StatefulWidget { + @override + State<_SettingsModulesSABnzbdHeadersRoute> createState() => _State(); +} + +class _State extends State<_SettingsModulesSABnzbdHeadersRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override @@ -27,7 +37,11 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Custom Headers'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', + title: 'Custom Headers', + ); Widget get _body => ValueListenableBuilder( valueListenable: Database.profilesBox.listenable(), @@ -39,7 +53,6 @@ class _State extends State { List get _headers => [ if((Database.currentProfileObject.sabnzbdHeaders ?? {}).isEmpty) _noHeaders, ..._list, - LSDivider(), SettingsModulesSABnzbdHeadersAddHeaderTile(), ]; diff --git a/lib/modules/settings/modules/modules_sabnzbd_headers/widgets/add_header_tile.dart b/lib/modules/settings/modules/modules_sabnzbd_headers/widgets/add_header_tile.dart index 1db3a075..6798c45c 100644 --- a/lib/modules/settings/modules/modules_sabnzbd_headers/widgets/add_header_tile.dart +++ b/lib/modules/settings/modules/modules_sabnzbd_headers/widgets/add_header_tile.dart @@ -20,7 +20,7 @@ class SettingsModulesSABnzbdHeadersAddHeaderTile extends StatelessWidget { _showCustomPrompt(context); break; default: - Logger.warning( + LunaLogger.warning( 'SettingsModulesSABnzbdHeadersAddHeaderTile', '_addPrompt', 'Unknown case: ${results[1]}', @@ -36,7 +36,7 @@ class SettingsModulesSABnzbdHeadersAddHeaderTile extends StatelessWidget { String _auth = base64.encode(utf8.encode('${results[1]}:${results[2]}')); _headers.addAll({'Authorization': 'Basic $_auth'}); Database.currentProfileObject.sabnzbdHeaders = _headers; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); } } @@ -46,7 +46,7 @@ class SettingsModulesSABnzbdHeadersAddHeaderTile extends StatelessWidget { Map _headers = (Database.currentProfileObject.sabnzbdHeaders ?? {}).cast(); _headers.addAll({results[1]: results[2]}); Database.currentProfileObject.sabnzbdHeaders = _headers; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); } } } \ No newline at end of file diff --git a/lib/modules/settings/modules/modules_sabnzbd_headers/widgets/header_tile.dart b/lib/modules/settings/modules/modules_sabnzbd_headers/widgets/header_tile.dart index 1ba79744..c8f0b452 100644 --- a/lib/modules/settings/modules/modules_sabnzbd_headers/widgets/header_tile.dart +++ b/lib/modules/settings/modules/modules_sabnzbd_headers/widgets/header_tile.dart @@ -17,7 +17,7 @@ class SettingsModulesSABnzbdHeadersHeaderTile extends StatelessWidget { subtitle: LSSubtitle(text: headerValue), trailing: LSIconButton( icon: Icons.delete, - color: LSColors.red, + color: LunaColours.red, onPressed: () async => _deleteHeader(context), ), ); @@ -28,7 +28,7 @@ class SettingsModulesSABnzbdHeadersHeaderTile extends StatelessWidget { Map _headers = (Database.currentProfileObject.sabnzbdHeaders ?? {}).cast(); _headers.remove(headerKey); Database.currentProfileObject.sabnzbdHeaders = _headers; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); LSSnackBar( context: context, message: headerKey, diff --git a/lib/modules/settings/modules/modules_search/route.dart b/lib/modules/settings/modules/modules_search/route.dart index 8e63297c..688f593a 100644 --- a/lib/modules/settings/modules/modules_search/route.dart +++ b/lib/modules/settings/modules/modules_search/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsModulesSearchRoute extends StatefulWidget { +class SettingsModulesSearchRouter { static const ROUTE_NAME = '/settings/modules/search'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsModulesSearchRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsModulesSearchRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsModulesSearchRouter._(); } -class _State extends State { +class _SettingsModulesSearchRoute extends StatefulWidget { + @override + State<_SettingsModulesSearchRoute> createState() => _State(); +} + +class _State extends State<_SettingsModulesSearchRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override Widget build(BuildContext context) => Scaffold( @@ -26,12 +36,14 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar( + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', title: 'Search', actions: [ LSIconButton( icon: Icons.brush, - onPressed: () async => SettingsRouter.router.navigateTo(context, SettingsCustomizationSearchRoute.ROUTE_NAME), + onPressed: () async => SettingsCustomizationSearchRouter.navigateTo(context), ), ] ); @@ -46,7 +58,6 @@ class _State extends State { ), if(Database.indexersBox.isEmpty) _noIndexers, ..._indexerList, - LSDivider(), _addIndexer, ], ), @@ -65,6 +76,6 @@ class _State extends State { Widget get _addIndexer => LSButton( text: 'Add New Indexer', - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsModulesSearchAddRoute.ROUTE_NAME), + onTap: () async => SettingsModulesSearchAddRouter.navigateTo(context), ); } diff --git a/lib/modules/settings/modules/modules_search/widgets/indexer_tile.dart b/lib/modules/settings/modules/modules_search/widgets/indexer_tile.dart index 65162beb..b44e5adf 100644 --- a/lib/modules/settings/modules/modules_search/widgets/indexer_tile.dart +++ b/lib/modules/settings/modules/modules_search/widgets/indexer_tile.dart @@ -20,8 +20,8 @@ class SettingsModulesSearchIndexerTile extends StatelessWidget { onTap: () async => _enterIndexer(context), ); - Future _enterIndexer(BuildContext context) async => SettingsRouter.router.navigateTo( + Future _enterIndexer(BuildContext context) async => SettingsModulesSearchEditRouter.navigateTo( context, - SettingsModulesSearchEditRoute.route(index: index), + index: index, ); } \ No newline at end of file diff --git a/lib/modules/settings/modules/modules_search_add/route.dart b/lib/modules/settings/modules/modules_search_add/route.dart index 8771b35a..6369cde2 100644 --- a/lib/modules/settings/modules/modules_search_add/route.dart +++ b/lib/modules/settings/modules/modules_search_add/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsModulesSearchAddRoute extends StatefulWidget { +class SettingsModulesSearchAddRouter { static const ROUTE_NAME = '/settings/modules/search/add'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsModulesSearchAddRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsModulesSearchAddRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsModulesSearchAddRouter._(); } -class _State extends State { +class _SettingsModulesSearchAddRoute extends StatefulWidget { + @override + State<_SettingsModulesSearchAddRoute> createState() => _State(); +} + +class _State extends State<_SettingsModulesSearchAddRoute> { final GlobalKey _scaffoldKey = GlobalKey(); IndexerHiveObject indexer = IndexerHiveObject.empty(); @@ -28,7 +38,11 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Add Indexer'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', + title: 'Add Indexer', + ); Widget get _body => LSListView( children: [ @@ -36,7 +50,6 @@ class _State extends State { _apiURL, _apiKey, _headers, - LSDivider(), _addIndexer, ], ); @@ -50,7 +63,7 @@ class _State extends State { ), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async { - List _values = await GlobalDialogs.editText(context, 'Display Name', prefill: indexer.displayName); + List _values = await LunaDialogs.editText(context, 'Display Name', prefill: indexer.displayName); setState(() => indexer.displayName = _values[0] ? _values[1] : indexer.displayName @@ -67,7 +80,7 @@ class _State extends State { ), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async { - List _values = await GlobalDialogs.editText(context, 'Indexer API Host', prefill: indexer.host); + List _values = await LunaDialogs.editText(context, 'Indexer API Host', prefill: indexer.host); setState(() => indexer.host = _values[0] ? _values[1] : indexer.host @@ -84,7 +97,7 @@ class _State extends State { ), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async { - List _values = await GlobalDialogs.editText(context, 'Indexer API Key', prefill: indexer.key); + List _values = await LunaDialogs.editText(context, 'Indexer API Key', prefill: indexer.key); setState(() => indexer.key = _values[0] ? _values[1] : indexer.key diff --git a/lib/modules/settings/modules/modules_search_add_headers/route.dart b/lib/modules/settings/modules/modules_search_add_headers/route.dart index fe46046b..88c3e285 100644 --- a/lib/modules/settings/modules/modules_search_add_headers/route.dart +++ b/lib/modules/settings/modules/modules_search_add_headers/route.dart @@ -25,13 +25,17 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Custom Headers'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: null, + hideLeading: true, + title: 'Custom Headers', + ); Widget get _body => LSListView( children: [ if((widget.indexer.headers ?? {}).isEmpty) _noHeaders, ..._list, - LSDivider(), _addHeader, ], ); @@ -51,7 +55,7 @@ class _State extends State { subtitle: LSSubtitle(text: value.toString()), trailing: LSIconButton( icon: Icons.delete, - color: LSColors.red, + color: LunaColours.red, onPressed: () async => _delete(key), ), ); @@ -71,7 +75,7 @@ class _State extends State { _showCustomPrompt(context); break; default: - Logger.warning( + LunaLogger.warning( 'SettingsModulesLidarrHeadersAddHeaderTile', '_addPrompt', 'Unknown case: ${results[1]}', diff --git a/lib/modules/settings/modules/modules_search_edit/route.dart b/lib/modules/settings/modules/modules_search_edit/route.dart index 933988c5..50928867 100644 --- a/lib/modules/settings/modules/modules_search_edit/route.dart +++ b/lib/modules/settings/modules/modules_search_edit/route.dart @@ -3,32 +3,44 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsModulesSearchEditRoute extends StatefulWidget { - final int index; - +class SettingsModulesSearchEditRouter { static const ROUTE_NAME = '/settings/modules/search/edit/:index'; + + static Future navigateTo(BuildContext context, { + @required int index, + }) async => LunaRouter.router.navigateTo( + context, + route(index: index), + ); + static String route({ @required int index, - }) => ROUTE_NAME.replaceFirst(':index', index.toString()); - - static void defineRoute(Router router) => router.define( + }) => ROUTE_NAME.replaceFirst(':index', index?.toString() ?? 0); + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsModulesSearchEditRoute( - index: int.tryParse(params['index'][0]), + handler: Handler(handlerFunc: (context, params) => _SettingsModulesSearchEditRoute( + index: params['index'] == null ? 0 : int.tryParse(params['index'][0] ?? 0), )), transitionType: LunaRouter.transitionType, ); - SettingsModulesSearchEditRoute({ + SettingsModulesSearchEditRouter._(); +} + +class _SettingsModulesSearchEditRoute extends StatefulWidget { + final int index; + + _SettingsModulesSearchEditRoute({ Key key, @required this.index, }) : super(key: key); @override - State createState() => _State(); + State<_SettingsModulesSearchEditRoute> createState() => _State(); } -class _State extends State { +class _State extends State<_SettingsModulesSearchEditRoute> { final GlobalKey _scaffoldKey = GlobalKey(); IndexerHiveObject _indexer; @@ -42,8 +54,8 @@ class _State extends State { try { _indexer = Database.indexersBox.getAt(widget.index); } catch (_) { - Logger.warning( - 'SettingsModulesSearchEditRoute', + LunaLogger.warning( + '_SettingsModulesSearchEditRoute', '_fetchIndexer', 'Unable to fetch indexer', ); @@ -57,7 +69,11 @@ class _State extends State { body: _indexer != null ? _body : _indexerNotFound, ); - Widget get _appBar => LSAppBar(title: 'Edit Indexer'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', + title: 'Edit Indexer', + ); Widget get _body => LSListView( children: [ @@ -65,7 +81,6 @@ class _State extends State { _apiURL, _apiKey, _headers, - LSDivider(), _deleteIndexer, ], ); @@ -81,7 +96,7 @@ class _State extends State { ), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async { - List _values = await GlobalDialogs.editText(context, 'Display Name', prefill: _indexer.displayName); + List _values = await LunaDialogs.editText(context, 'Display Name', prefill: _indexer.displayName); setState(() => _indexer.displayName = _values[0] ? _values[1] : _indexer.displayName @@ -99,7 +114,7 @@ class _State extends State { ), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async { - List _values = await GlobalDialogs.editText(context, 'Indexer API Host', prefill: _indexer.host); + List _values = await LunaDialogs.editText(context, 'Indexer API Host', prefill: _indexer.host); setState(() => _indexer.host = _values[0] ? _values[1] : _indexer.host @@ -117,7 +132,7 @@ class _State extends State { ), trailing: LSIconButton(icon: Icons.arrow_forward_ios), onTap: () async { - List _values = await GlobalDialogs.editText(context, 'Indexer API Key', prefill: _indexer.key); + List _values = await LunaDialogs.editText(context, 'Indexer API Key', prefill: _indexer.key); setState(() => _indexer.key = _values[0] ? _values[1] : _indexer.key @@ -137,7 +152,7 @@ class _State extends State { Widget get _deleteIndexer => LSButton( text: 'Delete Indexer', - backgroundColor: LSColors.red, + backgroundColor: LunaColours.red, onTap: () async => _delete(), ); diff --git a/lib/modules/settings/modules/modules_search_edit_headers/route.dart b/lib/modules/settings/modules/modules_search_edit_headers/route.dart index b67d14e2..f63c0719 100644 --- a/lib/modules/settings/modules/modules_search_edit_headers/route.dart +++ b/lib/modules/settings/modules/modules_search_edit_headers/route.dart @@ -25,13 +25,17 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Custom Headers'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: null, + hideLeading: true, + title: 'Custom Headers', + ); Widget get _body => LSListView( children: [ if((widget.indexer.headers ?? {}).isEmpty) _noHeaders, ..._list, - LSDivider(), _addHeader, ], ); @@ -51,7 +55,7 @@ class _State extends State { subtitle: LSSubtitle(text: value.toString()), trailing: LSIconButton( icon: Icons.delete, - color: LSColors.red, + color: LunaColours.red, onPressed: () async => _delete(key), ), ); @@ -71,7 +75,7 @@ class _State extends State { _showCustomPrompt(context); break; default: - Logger.warning( + LunaLogger.warning( 'SettingsModulesLidarrHeadersAddHeaderTile', '_addPrompt', 'Unknown case: ${results[1]}', diff --git a/lib/modules/settings/modules/modules_sonarr/route.dart b/lib/modules/settings/modules/modules_sonarr/route.dart index 5b1bb61b..5acfff1a 100644 --- a/lib/modules/settings/modules/modules_sonarr/route.dart +++ b/lib/modules/settings/modules/modules_sonarr/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsModulesSonarrRoute extends StatefulWidget { +class SettingsModulesSonarrRouter { static const ROUTE_NAME = '/settings/modules/sonarr'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsModulesSonarrRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsModulesSonarrRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsModulesSonarrRouter._(); } -class _State extends State { +class _SettingsModulesSonarrRoute extends StatefulWidget { + @override + State<_SettingsModulesSonarrRoute> createState() => _State(); +} + +class _State extends State<_SettingsModulesSonarrRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override @@ -27,12 +37,14 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar( + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', title: 'Sonarr', actions: [ LSIconButton( icon: Icons.brush, - onPressed: () async => SettingsRouter.router.navigateTo(context, SettingsCustomizationSonarrRoute.ROUTE_NAME), + onPressed: () async => SettingsCustomizationSonarrRouter.navigateTo(context), ), ] ); @@ -41,30 +53,17 @@ class _State extends State { valueListenable: Database.profilesBox.listenable(), builder: (context, box, _) => LSListView( children: [ - ..._mandatory, - LSDivider(), + ..._configuration, SettingsModulesSonarrTestConnectionTile(), - ..._advanced, ], ), ); - List get _mandatory => [ - LSHeader( - text: 'Mandatory', - subtitle: 'Configuration that is required for functionality', - ), + List get _configuration => [ SettingsModulesSonarrEnabledTile(), SettingsModulesSonarrHostTile(), SettingsModulesSonarrAPIKeyTile(), - ]; - - List get _advanced => [ - LSHeader( - text: 'Advanced', - subtitle: 'Options for non-standard networking configurations', - ), SettingsModulesSonarrCustomHeadersTile(), - SettingsModulesSonarrStrictTLSTile(), + SettingsModulesSonarrEnableVersion3Tile(), ]; } diff --git a/lib/modules/settings/modules/modules_sonarr/widgets.dart b/lib/modules/settings/modules/modules_sonarr/widgets.dart index 84d587e2..b1b01476 100644 --- a/lib/modules/settings/modules/modules_sonarr/widgets.dart +++ b/lib/modules/settings/modules/modules_sonarr/widgets.dart @@ -1,6 +1,6 @@ export 'widgets/apikey_tile.dart'; export 'widgets/custom_headers_tile.dart'; +export 'widgets/enable_version_3_tile.dart'; export 'widgets/enabled_tile.dart'; export 'widgets/host_tile.dart'; -export 'widgets/strict_tls_tile.dart'; export 'widgets/test_connection_tile.dart'; diff --git a/lib/modules/settings/modules/modules_sonarr/widgets/apikey_tile.dart b/lib/modules/settings/modules/modules_sonarr/widgets/apikey_tile.dart index 4bdb55eb..df6390a3 100644 --- a/lib/modules/settings/modules/modules_sonarr/widgets/apikey_tile.dart +++ b/lib/modules/settings/modules/modules_sonarr/widgets/apikey_tile.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; class SettingsModulesSonarrAPIKeyTile extends StatelessWidget { @override @@ -15,14 +16,15 @@ class SettingsModulesSonarrAPIKeyTile extends StatelessWidget { ); Future _changeKey(BuildContext context) async { - List _values = await GlobalDialogs.editText( + List _values = await LunaDialogs.editText( context, 'Sonarr API Key', prefill: Database.currentProfileObject.sonarrKey ?? '', ); if(_values[0]) { Database.currentProfileObject.sonarrKey = _values[1]; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); + Provider.of(context, listen: false).reset(); } } } \ No newline at end of file diff --git a/lib/modules/settings/modules/modules_sonarr/widgets/custom_headers_tile.dart b/lib/modules/settings/modules/modules_sonarr/widgets/custom_headers_tile.dart index 36295d3e..b17b5f94 100644 --- a/lib/modules/settings/modules/modules_sonarr/widgets/custom_headers_tile.dart +++ b/lib/modules/settings/modules/modules_sonarr/widgets/custom_headers_tile.dart @@ -8,6 +8,6 @@ class SettingsModulesSonarrCustomHeadersTile extends StatelessWidget { title: LSTitle(text: 'Custom Headers'), subtitle: LSSubtitle(text: 'Add Custom Headers to Requests'), trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsModulesSonarrHeadersRoute.ROUTE_NAME), + onTap: () async => SettingsModulesSonarrHeadersRouter.navigateTo(context), ); } diff --git a/lib/modules/settings/modules/modules_sonarr/widgets/enable_version_3_tile.dart b/lib/modules/settings/modules/modules_sonarr/widgets/enable_version_3_tile.dart new file mode 100644 index 00000000..37ddd92e --- /dev/null +++ b/lib/modules/settings/modules/modules_sonarr/widgets/enable_version_3_tile.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SettingsModulesSonarrEnableVersion3Tile extends StatelessWidget { + @override + Widget build(BuildContext context) => LSCardTile( + title: LSTitle(text: 'Sonarr v3 Features'), + subtitle: LSSubtitle(text: 'Enable Version 3 Specific Features',), + trailing: Switch( + value: Database.currentProfileObject.sonarrVersion3 ?? false, + onChanged: (value) { + Database.currentProfileObject.sonarrVersion3 = value; + Database.currentProfileObject.save(); + Provider.of(context, listen: false).reset(); + }, + ), + ); +} diff --git a/lib/modules/settings/modules/modules_sonarr/widgets/enabled_tile.dart b/lib/modules/settings/modules/modules_sonarr/widgets/enabled_tile.dart index 014bc578..4f0ea9a1 100644 --- a/lib/modules/settings/modules/modules_sonarr/widgets/enabled_tile.dart +++ b/lib/modules/settings/modules/modules_sonarr/widgets/enabled_tile.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; class SettingsModulesSonarrEnabledTile extends StatelessWidget { @override @@ -9,7 +10,8 @@ class SettingsModulesSonarrEnabledTile extends StatelessWidget { value: Database.currentProfileObject.sonarrEnabled ?? false, onChanged: (value) { Database.currentProfileObject.sonarrEnabled = value; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); + Provider.of(context, listen: false).reset(); }, ), ); diff --git a/lib/modules/settings/modules/modules_sonarr/widgets/host_tile.dart b/lib/modules/settings/modules/modules_sonarr/widgets/host_tile.dart index 9b929bbf..21901715 100644 --- a/lib/modules/settings/modules/modules_sonarr/widgets/host_tile.dart +++ b/lib/modules/settings/modules/modules_sonarr/widgets/host_tile.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; +import 'package:lunasea/modules/sonarr.dart'; class SettingsModulesSonarrHostTile extends StatelessWidget { @override @@ -23,7 +24,8 @@ class SettingsModulesSonarrHostTile extends StatelessWidget { ); if(_values[0]) { Database.currentProfileObject.sonarrHost = _values[1]; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); + Provider.of(context, listen: false).reset(); } } -} \ No newline at end of file +} diff --git a/lib/modules/settings/modules/modules_sonarr/widgets/strict_tls_tile.dart b/lib/modules/settings/modules/modules_sonarr/widgets/strict_tls_tile.dart deleted file mode 100644 index 40b42e4c..00000000 --- a/lib/modules/settings/modules/modules_sonarr/widgets/strict_tls_tile.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/settings.dart'; - -class SettingsModulesSonarrStrictTLSTile extends StatelessWidget { - @override - Widget build(BuildContext context) => LSCardTile( - title: LSTitle(text: 'Strict SSL/TLS Validation'), - subtitle: LSSubtitle(text: 'For Invalid Certificates'), - trailing: Switch( - value: Database.currentProfileObject.sonarrStrictTLS ?? true, - onChanged: (value) async => _onChanged(context, value), - ), - ); - - Future _onChanged(BuildContext context, bool value) async { - if(value) { - Database.currentProfileObject.sonarrStrictTLS = value; - Database.currentProfileObject.save(context: context); - } else { - List _values = await SettingsDialogs.toggleStrictTLS(context); - if(_values[0]) { - Database.currentProfileObject.sonarrStrictTLS = value; - Database.currentProfileObject.save(context: context); - } - } - } -} \ No newline at end of file diff --git a/lib/modules/settings/modules/modules_sonarr/widgets/test_connection_tile.dart b/lib/modules/settings/modules/modules_sonarr/widgets/test_connection_tile.dart index e43797a5..24b27fc9 100644 --- a/lib/modules/settings/modules/modules_sonarr/widgets/test_connection_tile.dart +++ b/lib/modules/settings/modules/modules_sonarr/widgets/test_connection_tile.dart @@ -9,7 +9,26 @@ class SettingsModulesSonarrTestConnectionTile extends StatelessWidget { onTap: () async => _testConnection(context), ); - Future _testConnection(BuildContext context) async => await SonarrAPI.from(Database.currentProfileObject).testConnection() - ? LSSnackBar(context: context, title: 'Connected Successfully', message: 'Sonarr is ready to use with LunaSea', type: SNACKBAR_TYPE.success) - : LSSnackBar(context: context, title: 'Connection Test Failed', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure); + Future _testConnection(BuildContext context) async { + SonarrState state = Provider.of(context, listen: false); + if(!state.enabled) { + LSSnackBar(context: context, title: 'Sonarr Not Enabled', message: 'Sonarr needs to be enabled', type: SNACKBAR_TYPE.failure); + return; + } + if(state.host == null || state.host.isEmpty) { + LSSnackBar(context: context, title: 'Host Required', message: 'Host is required to connect to Sonarr', type: SNACKBAR_TYPE.failure); + return; + } + if(state.apiKey == null || state.apiKey.isEmpty) { + LSSnackBar(context: context, title: 'API Key Required', message: 'API key is required to connect to Sonarr', type: SNACKBAR_TYPE.failure); + return; + } + state.api.system.getStatus() + .then((_) { + LSSnackBar(context: context, title: 'Connected Successfully', message: 'Sonarr is ready to use with LunaSea', type: SNACKBAR_TYPE.success); + }).catchError((error, trace) { + LunaLogger.error('SettingsModulesSonarrTestConnectionTile', '_testConnection', 'Failed Connection', error, trace, uploadToSentry: false); + LSSnackBar(context: context, title: 'Connection Test Failed', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure); + }); + } } diff --git a/lib/modules/settings/modules/modules_sonarr_headers/route.dart b/lib/modules/settings/modules/modules_sonarr_headers/route.dart index 4b77795f..9238fb6a 100644 --- a/lib/modules/settings/modules/modules_sonarr_headers/route.dart +++ b/lib/modules/settings/modules/modules_sonarr_headers/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsModulesSonarrHeadersRoute extends StatefulWidget { +class SettingsModulesSonarrHeadersRouter { static const ROUTE_NAME = '/settings/modules/sonarr/headers'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsModulesSonarrHeadersRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsModulesSonarrHeadersRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsModulesSonarrHeadersRouter._(); } -class _State extends State { +class _SettingsModulesSonarrHeadersRoute extends StatefulWidget { + @override + State<_SettingsModulesSonarrHeadersRoute> createState() => _State(); +} + +class _State extends State<_SettingsModulesSonarrHeadersRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override @@ -27,7 +37,11 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Custom Headers'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', + title: 'Custom Headers', + ); Widget get _body => ValueListenableBuilder( valueListenable: Database.profilesBox.listenable(), @@ -39,7 +53,6 @@ class _State extends State { List get _headers => [ if((Database.currentProfileObject.sonarrHeaders ?? {}).isEmpty) _noHeaders, ..._list, - LSDivider(), SettingsModulesSonarrHeadersAddHeaderTile(), ]; diff --git a/lib/modules/settings/modules/modules_sonarr_headers/widgets/add_header_tile.dart b/lib/modules/settings/modules/modules_sonarr_headers/widgets/add_header_tile.dart index 2517822f..28cd64c5 100644 --- a/lib/modules/settings/modules/modules_sonarr_headers/widgets/add_header_tile.dart +++ b/lib/modules/settings/modules/modules_sonarr_headers/widgets/add_header_tile.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; +import 'package:lunasea/modules/sonarr.dart'; class SettingsModulesSonarrHeadersAddHeaderTile extends StatelessWidget { @override @@ -20,7 +21,7 @@ class SettingsModulesSonarrHeadersAddHeaderTile extends StatelessWidget { _showCustomPrompt(context); break; default: - Logger.warning( + LunaLogger.warning( 'SettingsModulesSonarrHeadersAddHeaderTile', '_addPrompt', 'Unknown case: ${results[1]}', @@ -36,7 +37,8 @@ class SettingsModulesSonarrHeadersAddHeaderTile extends StatelessWidget { String _auth = base64.encode(utf8.encode('${results[1]}:${results[2]}')); _headers.addAll({'Authorization': 'Basic $_auth'}); Database.currentProfileObject.sonarrHeaders = _headers; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); + Provider.of(context, listen: false).reset(); } } @@ -46,7 +48,8 @@ class SettingsModulesSonarrHeadersAddHeaderTile extends StatelessWidget { Map _headers = (Database.currentProfileObject.sonarrHeaders ?? {}).cast(); _headers.addAll({results[1]: results[2]}); Database.currentProfileObject.sonarrHeaders = _headers; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); + Provider.of(context, listen: false).reset(); } } -} \ No newline at end of file +} diff --git a/lib/modules/settings/modules/modules_sonarr_headers/widgets/header_tile.dart b/lib/modules/settings/modules/modules_sonarr_headers/widgets/header_tile.dart index b8714dac..628dec08 100644 --- a/lib/modules/settings/modules/modules_sonarr_headers/widgets/header_tile.dart +++ b/lib/modules/settings/modules/modules_sonarr_headers/widgets/header_tile.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; +import 'package:lunasea/modules/sonarr.dart'; class SettingsModulesSonarrHeadersHeaderTile extends StatelessWidget { final String headerKey; @@ -17,7 +18,7 @@ class SettingsModulesSonarrHeadersHeaderTile extends StatelessWidget { subtitle: LSSubtitle(text: headerValue), trailing: LSIconButton( icon: Icons.delete, - color: LSColors.red, + color: LunaColours.red, onPressed: () async => _deleteHeader(context), ), ); @@ -28,7 +29,8 @@ class SettingsModulesSonarrHeadersHeaderTile extends StatelessWidget { Map _headers = (Database.currentProfileObject.sonarrHeaders ?? {}).cast(); _headers.remove(headerKey); Database.currentProfileObject.sonarrHeaders = _headers; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); + Provider.of(context, listen: false).reset(); LSSnackBar( context: context, message: headerKey, diff --git a/lib/modules/settings/modules/modules_tautulli/route.dart b/lib/modules/settings/modules/modules_tautulli/route.dart index 4e049b70..6252b965 100644 --- a/lib/modules/settings/modules/modules_tautulli/route.dart +++ b/lib/modules/settings/modules/modules_tautulli/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsModulesTautulliRoute extends StatefulWidget { +class SettingsModulesTautulliRouter { static const ROUTE_NAME = '/settings/modules/tautulli'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsModulesTautulliRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsModulesTautulliRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsModulesTautulliRouter._(); } -class _State extends State { +class _SettingsModulesTautulliRoute extends StatefulWidget { + @override + State<_SettingsModulesTautulliRoute> createState() => _State(); +} + +class _State extends State<_SettingsModulesTautulliRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override @@ -27,12 +37,14 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar( + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', title: 'Tautulli', actions: [ LSIconButton( icon: Icons.brush, - onPressed: () async => SettingsRouter.router.navigateTo(context, SettingsCustomizationTautulliRoute.ROUTE_NAME), + onPressed: () async => SettingsCustomizationTautulliRouter.navigateTo(context), ), ] ); @@ -41,30 +53,16 @@ class _State extends State { valueListenable: Database.profilesBox.listenable(), builder: (context, box, _) => LSListView( children: [ - ..._mandatory, - LSDivider(), + ..._configuration, SettingsModulesTautulliTestConnectionTile(), - ..._advanced, ], ), ); - List get _mandatory => [ - LSHeader( - text: 'Mandatory', - subtitle: 'Configuration that is required for functionality', - ), + List get _configuration => [ SettingsModulesTautulliEnabledTile(), SettingsModulesTautulliHostTile(), SettingsModulesTautulliAPIKeyTile(), - ]; - - List get _advanced => [ - LSHeader( - text: 'Advanced', - subtitle: 'Options for non-standard networking configurations', - ), SettingsModulesTautulliCustomHeadersTile(), - SettingsModulesTautulliStrictTLSTile(), ]; } diff --git a/lib/modules/settings/modules/modules_tautulli/widgets.dart b/lib/modules/settings/modules/modules_tautulli/widgets.dart index 84d587e2..9d21dbdd 100644 --- a/lib/modules/settings/modules/modules_tautulli/widgets.dart +++ b/lib/modules/settings/modules/modules_tautulli/widgets.dart @@ -2,5 +2,4 @@ export 'widgets/apikey_tile.dart'; export 'widgets/custom_headers_tile.dart'; export 'widgets/enabled_tile.dart'; export 'widgets/host_tile.dart'; -export 'widgets/strict_tls_tile.dart'; export 'widgets/test_connection_tile.dart'; diff --git a/lib/modules/settings/modules/modules_tautulli/widgets/apikey_tile.dart b/lib/modules/settings/modules/modules_tautulli/widgets/apikey_tile.dart index 1723b875..e38bde82 100644 --- a/lib/modules/settings/modules/modules_tautulli/widgets/apikey_tile.dart +++ b/lib/modules/settings/modules/modules_tautulli/widgets/apikey_tile.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/tautulli.dart'; class SettingsModulesTautulliAPIKeyTile extends StatelessWidget { @override @@ -15,14 +16,15 @@ class SettingsModulesTautulliAPIKeyTile extends StatelessWidget { ); Future _changeKey(BuildContext context) async { - List _values = await GlobalDialogs.editText( + List _values = await LunaDialogs.editText( context, 'Tautulli API Key', prefill: Database.currentProfileObject.tautulliKey ?? '', ); if(_values[0]) { Database.currentProfileObject.tautulliKey = _values[1]; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); + Provider.of(context, listen: false).reset(); } } } \ No newline at end of file diff --git a/lib/modules/settings/modules/modules_tautulli/widgets/custom_headers_tile.dart b/lib/modules/settings/modules/modules_tautulli/widgets/custom_headers_tile.dart index 33e67f91..781540a2 100644 --- a/lib/modules/settings/modules/modules_tautulli/widgets/custom_headers_tile.dart +++ b/lib/modules/settings/modules/modules_tautulli/widgets/custom_headers_tile.dart @@ -8,6 +8,6 @@ class SettingsModulesTautulliCustomHeadersTile extends StatelessWidget { title: LSTitle(text: 'Custom Headers'), subtitle: LSSubtitle(text: 'Add Custom Headers to Requests'), trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsModulesTautulliHeadersRoute.ROUTE_NAME), + onTap: () async => SettingsModulesTautulliHeadersRouter.navigateTo(context), ); } diff --git a/lib/modules/settings/modules/modules_tautulli/widgets/enabled_tile.dart b/lib/modules/settings/modules/modules_tautulli/widgets/enabled_tile.dart index a9e7ef78..8208894f 100644 --- a/lib/modules/settings/modules/modules_tautulli/widgets/enabled_tile.dart +++ b/lib/modules/settings/modules/modules_tautulli/widgets/enabled_tile.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/tautulli.dart'; class SettingsModulesTautulliEnabledTile extends StatelessWidget { @override @@ -9,7 +10,8 @@ class SettingsModulesTautulliEnabledTile extends StatelessWidget { value: Database.currentProfileObject.tautulliEnabled ?? false, onChanged: (value) { Database.currentProfileObject.tautulliEnabled = value; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); + Provider.of(context, listen: false).reset(); }, ), ); diff --git a/lib/modules/settings/modules/modules_tautulli/widgets/host_tile.dart b/lib/modules/settings/modules/modules_tautulli/widgets/host_tile.dart index cf015eca..05e04748 100644 --- a/lib/modules/settings/modules/modules_tautulli/widgets/host_tile.dart +++ b/lib/modules/settings/modules/modules_tautulli/widgets/host_tile.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; +import 'package:lunasea/modules/tautulli.dart'; class SettingsModulesTautulliHostTile extends StatelessWidget { @override @@ -23,7 +24,8 @@ class SettingsModulesTautulliHostTile extends StatelessWidget { ); if(_values[0]) { Database.currentProfileObject.tautulliHost = _values[1]; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); + Provider.of(context, listen: false).reset(); } } -} \ No newline at end of file +} diff --git a/lib/modules/settings/modules/modules_tautulli/widgets/strict_tls_tile.dart b/lib/modules/settings/modules/modules_tautulli/widgets/strict_tls_tile.dart deleted file mode 100644 index 5180209e..00000000 --- a/lib/modules/settings/modules/modules_tautulli/widgets/strict_tls_tile.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/settings.dart'; - -class SettingsModulesTautulliStrictTLSTile extends StatelessWidget { - @override - Widget build(BuildContext context) => LSCardTile( - title: LSTitle(text: 'Strict SSL/TLS Validation'), - subtitle: LSSubtitle(text: 'For Invalid Certificates'), - trailing: Switch( - value: Database.currentProfileObject.tautulliStrictTLS ?? true, - onChanged: (value) async => _onChanged(context, value), - ), - ); - - Future _onChanged(BuildContext context, bool value) async { - if(value) { - Database.currentProfileObject.tautulliStrictTLS = value; - Database.currentProfileObject.save(context: context); - } else { - List _values = await SettingsDialogs.toggleStrictTLS(context); - if(_values[0]) { - Database.currentProfileObject.tautulliStrictTLS = value; - Database.currentProfileObject.save(context: context); - } - } - } -} \ No newline at end of file diff --git a/lib/modules/settings/modules/modules_tautulli/widgets/test_connection_tile.dart b/lib/modules/settings/modules/modules_tautulli/widgets/test_connection_tile.dart index ddd2f02d..ae8614ad 100644 --- a/lib/modules/settings/modules/modules_tautulli/widgets/test_connection_tile.dart +++ b/lib/modules/settings/modules/modules_tautulli/widgets/test_connection_tile.dart @@ -27,7 +27,7 @@ class SettingsModulesTautulliTestConnectionTile extends StatelessWidget { .then((_) { LSSnackBar(context: context, title: 'Connected Successfully', message: 'Tautulli is ready to use with LunaSea', type: SNACKBAR_TYPE.success); }).catchError((error, trace) { - Logger.error('SettingsModulesTautulli', '_testConnection', 'Failed Connection', error, trace, uploadToSentry: false); + LunaLogger.error('SettingsModulesTautulliTestConnectionTile', '_testConnection', 'Failed Connection', error, trace, uploadToSentry: false); LSSnackBar(context: context, title: 'Connection Test Failed', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure); }); } diff --git a/lib/modules/settings/modules/modules_tautulli_headers/route.dart b/lib/modules/settings/modules/modules_tautulli_headers/route.dart index 4953c9e1..bebfd125 100644 --- a/lib/modules/settings/modules/modules_tautulli_headers/route.dart +++ b/lib/modules/settings/modules/modules_tautulli_headers/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsModulesTautulliHeadersRoute extends StatefulWidget { +class SettingsModulesTautulliHeadersRouter { static const ROUTE_NAME = '/settings/modules/tautulli/headers'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsModulesTautulliHeadersRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsModulesTautulliHeadersRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsModulesTautulliHeadersRouter._(); } -class _State extends State { +class _SettingsModulesTautulliHeadersRoute extends StatefulWidget { + @override + State<_SettingsModulesTautulliHeadersRoute> createState() => _State(); +} + +class _State extends State<_SettingsModulesTautulliHeadersRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override @@ -27,7 +37,11 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Custom Headers'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', + title: 'Custom Headers', + ); Widget get _body => ValueListenableBuilder( valueListenable: Database.profilesBox.listenable(), @@ -39,7 +53,6 @@ class _State extends State { List get _headers => [ if((Database.currentProfileObject.tautulliHeaders ?? {}).isEmpty) _noHeaders, ..._list, - LSDivider(), SettingsModulesTautulliHeadersAddHeaderTile(), ]; diff --git a/lib/modules/settings/modules/modules_tautulli_headers/widgets/add_header_tile.dart b/lib/modules/settings/modules/modules_tautulli_headers/widgets/add_header_tile.dart index 79d86c12..7c7ca0d3 100644 --- a/lib/modules/settings/modules/modules_tautulli_headers/widgets/add_header_tile.dart +++ b/lib/modules/settings/modules/modules_tautulli_headers/widgets/add_header_tile.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; +import 'package:lunasea/modules/tautulli.dart'; class SettingsModulesTautulliHeadersAddHeaderTile extends StatelessWidget { @override @@ -20,7 +21,7 @@ class SettingsModulesTautulliHeadersAddHeaderTile extends StatelessWidget { _showCustomPrompt(context); break; default: - Logger.warning( + LunaLogger.warning( 'SettingsModulesTautulliHeadersAddHeaderTile', '_addPrompt', 'Unknown case: ${results[1]}', @@ -36,7 +37,8 @@ class SettingsModulesTautulliHeadersAddHeaderTile extends StatelessWidget { String _auth = base64.encode(utf8.encode('${results[1]}:${results[2]}')); _headers.addAll({'Authorization': 'Basic $_auth'}); Database.currentProfileObject.tautulliHeaders = _headers; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); + Provider.of(context, listen: false).reset(); } } @@ -46,7 +48,8 @@ class SettingsModulesTautulliHeadersAddHeaderTile extends StatelessWidget { Map _headers = (Database.currentProfileObject.tautulliHeaders ?? {}).cast(); _headers.addAll({results[1]: results[2]}); Database.currentProfileObject.tautulliHeaders = _headers; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); + Provider.of(context, listen: false).reset(); } } } \ No newline at end of file diff --git a/lib/modules/settings/modules/modules_tautulli_headers/widgets/header_tile.dart b/lib/modules/settings/modules/modules_tautulli_headers/widgets/header_tile.dart index c8c9a48b..7d54fdeb 100644 --- a/lib/modules/settings/modules/modules_tautulli_headers/widgets/header_tile.dart +++ b/lib/modules/settings/modules/modules_tautulli_headers/widgets/header_tile.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; +import 'package:lunasea/modules/tautulli.dart'; class SettingsModulesTautulliHeadersHeaderTile extends StatelessWidget { final String headerKey; @@ -17,7 +18,7 @@ class SettingsModulesTautulliHeadersHeaderTile extends StatelessWidget { subtitle: LSSubtitle(text: headerValue), trailing: LSIconButton( icon: Icons.delete, - color: LSColors.red, + color: LunaColours.red, onPressed: () async => _deleteHeader(context), ), ); @@ -28,7 +29,8 @@ class SettingsModulesTautulliHeadersHeaderTile extends StatelessWidget { Map _headers = (Database.currentProfileObject.tautulliHeaders ?? {}).cast(); _headers.remove(headerKey); Database.currentProfileObject.tautulliHeaders = _headers; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); + Provider.of(context, listen: false).reset(); LSSnackBar( context: context, message: headerKey, diff --git a/lib/modules/settings/modules/modules_wakeonlan/route.dart b/lib/modules/settings/modules/modules_wakeonlan/route.dart index b71444a5..f7468a3d 100644 --- a/lib/modules/settings/modules/modules_wakeonlan/route.dart +++ b/lib/modules/settings/modules/modules_wakeonlan/route.dart @@ -3,21 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsModulesWakeOnLANRoute extends StatefulWidget { +class SettingsModulesWakeOnLANRouter { static const ROUTE_NAME = '/settings/modules/wakeonlan'; - static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsModulesWakeOnLANRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsModulesWakeOnLANRoute()), transitionType: LunaRouter.transitionType, ); - @override - State createState() => _State(); + SettingsModulesWakeOnLANRouter._(); } -class _State extends State { +class _SettingsModulesWakeOnLANRoute extends StatefulWidget { + @override + State<_SettingsModulesWakeOnLANRoute> createState() => _State(); +} + +class _State extends State<_SettingsModulesWakeOnLANRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override @@ -27,22 +37,22 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Wake on LAN'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', + title: 'Wake on LAN', + ); Widget get _body => ValueListenableBuilder( valueListenable: Database.profilesBox.listenable(), builder: (context, box, _) => LSListView( children: [ - ..._mandatory, + ..._configuration, ], ), ); - List get _mandatory => [ - LSHeader( - text: 'Mandatory', - subtitle: 'Configuration that is required for functionality', - ), + List get _configuration => [ SettingsModulesWakeOnLANEnabledTile(), SettingsModulesWakeOnLANBroadcastAddressTile(), SettingsModulesWakeOnLANMACAddressTile(), diff --git a/lib/modules/settings/modules/modules_wakeonlan/widgets/broadcast_address_tile.dart b/lib/modules/settings/modules/modules_wakeonlan/widgets/broadcast_address_tile.dart index 64007fb3..ce87841f 100644 --- a/lib/modules/settings/modules/modules_wakeonlan/widgets/broadcast_address_tile.dart +++ b/lib/modules/settings/modules/modules_wakeonlan/widgets/broadcast_address_tile.dart @@ -19,7 +19,7 @@ class SettingsModulesWakeOnLANBroadcastAddressTile extends StatelessWidget { List _values = await SettingsDialogs.editBroadcastAddress(context, Database.currentProfileObject.wakeOnLANBroadcastAddress ?? ''); if(_values[0]) { Database.currentProfileObject.wakeOnLANBroadcastAddress = _values[1]; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); } } } \ No newline at end of file diff --git a/lib/modules/settings/modules/modules_wakeonlan/widgets/enabled_tile.dart b/lib/modules/settings/modules/modules_wakeonlan/widgets/enabled_tile.dart index f71fb9d6..a7f299e8 100644 --- a/lib/modules/settings/modules/modules_wakeonlan/widgets/enabled_tile.dart +++ b/lib/modules/settings/modules/modules_wakeonlan/widgets/enabled_tile.dart @@ -9,7 +9,7 @@ class SettingsModulesWakeOnLANEnabledTile extends StatelessWidget { value: Database.currentProfileObject.wakeOnLANEnabled ?? false, onChanged: (value) { Database.currentProfileObject.wakeOnLANEnabled = value; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); }, ), ); diff --git a/lib/modules/settings/modules/modules_wakeonlan/widgets/mac_address_tile.dart b/lib/modules/settings/modules/modules_wakeonlan/widgets/mac_address_tile.dart index ebb94f81..8868af83 100644 --- a/lib/modules/settings/modules/modules_wakeonlan/widgets/mac_address_tile.dart +++ b/lib/modules/settings/modules/modules_wakeonlan/widgets/mac_address_tile.dart @@ -19,7 +19,7 @@ class SettingsModulesWakeOnLANMACAddressTile extends StatelessWidget { List _values = await SettingsDialogs.editMACAddress(context, Database.currentProfileObject.wakeOnLANMACAddress ?? ''); if(_values[0]) { Database.currentProfileObject.wakeOnLANMACAddress = _values[1]; - Database.currentProfileObject.save(context: context); + Database.currentProfileObject.save(); } } } \ No newline at end of file diff --git a/lib/modules/settings/modules/profiles/route.dart b/lib/modules/settings/modules/profiles/route.dart index 7d1193df..7a03eb59 100644 --- a/lib/modules/settings/modules/profiles/route.dart +++ b/lib/modules/settings/modules/profiles/route.dart @@ -3,25 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsProfilesRoute extends StatefulWidget { +class SettingsProfilesRouter { static const ROUTE_NAME = '/settings/profiles'; + + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsProfilesRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsProfilesRoute()), transitionType: LunaRouter.transitionType, ); - SettingsProfilesRoute({ - Key key, - }): super(key: key); - - @override - State createState() => _State(); + SettingsProfilesRouter._(); } -class _State extends State with AutomaticKeepAliveClientMixin { +class _SettingsProfilesRoute extends StatefulWidget { + @override + State<_SettingsProfilesRoute> createState() => _State(); +} + +class _State extends State<_SettingsProfilesRoute> with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; @@ -34,12 +40,15 @@ class _State extends State with AutomaticKeepAliveClientM ); } - Widget get _appBar => LSAppBar(title: 'Profiles'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', + title: 'Profiles', + ); Widget get _body => LSListView( children: [ SettingsProfileEnabledTile(), - LSDivider(), SettingsProfileAddTile(), SettingsProfileRenameTile(), SettingsProfileDeleteTile(), diff --git a/lib/modules/settings/modules/profiles/widgets/add_tile.dart b/lib/modules/settings/modules/profiles/widgets/add_tile.dart index 5da31a4d..eec2f910 100644 --- a/lib/modules/settings/modules/profiles/widgets/add_tile.dart +++ b/lib/modules/settings/modules/profiles/widgets/add_tile.dart @@ -21,8 +21,7 @@ class SettingsProfileAddTile extends StatelessWidget { LSSnackBar(context: context, title: 'Unable to Add Profile', message: 'The new profile name cannot be empty', type: SNACKBAR_TYPE.failure); } else { Database.profilesBox.put(_values[1], ProfileHiveObject.empty()); - LunaSeaDatabaseValue.ENABLED_PROFILE.put(_values[1]); - Providers.reset(context); + LunaProfile.changeProfile(context, _values[1]); LSSnackBar(context: context, title: 'Profile Added', message: '"${_values[1]}" has been added', type: SNACKBAR_TYPE.success); } } diff --git a/lib/modules/settings/modules/profiles/widgets/enabled_tile.dart b/lib/modules/settings/modules/profiles/widgets/enabled_tile.dart index a2a0c8d0..a7735b39 100644 --- a/lib/modules/settings/modules/profiles/widgets/enabled_tile.dart +++ b/lib/modules/settings/modules/profiles/widgets/enabled_tile.dart @@ -19,17 +19,7 @@ class SettingsProfileEnabledTile extends StatelessWidget { context, Database.profilesBox.keys.map((x) => x as String).toList()..sort((a,b) => a.toLowerCase().compareTo(b.toLowerCase())), ); - if(values[0]) { - if(values[1] != LunaSeaDatabaseValue.ENABLED_PROFILE.data) { - LunaSeaDatabaseValue.ENABLED_PROFILE.put(values[1]); - Providers.reset(context); - } - LSSnackBar( - context: context, - title: 'Changed Profile', - message: 'Using profile "${values[1]}"', - type: SNACKBAR_TYPE.info, - ); - } + if(values[0] && values[1] != LunaSeaDatabaseValue.ENABLED_PROFILE.data) + LunaProfile.changeProfile(context, values[1]); } } diff --git a/lib/modules/settings/modules/profiles/widgets/rename_tile.dart b/lib/modules/settings/modules/profiles/widgets/rename_tile.dart index ba42dae7..000feefa 100644 --- a/lib/modules/settings/modules/profiles/widgets/rename_tile.dart +++ b/lib/modules/settings/modules/profiles/widgets/rename_tile.dart @@ -27,9 +27,8 @@ class SettingsProfileRenameTile extends StatelessWidget { } else { ProfileHiveObject obj = Database.profilesBox.get(old); Database.profilesBox.put(_values[1], ProfileHiveObject.from(obj)); - if(LunaSeaDatabaseValue.ENABLED_PROFILE.data == old) LunaSeaDatabaseValue.ENABLED_PROFILE.put(_values[1]); + if(LunaSeaDatabaseValue.ENABLED_PROFILE.data == old) LunaProfile.changeProfile(context, _values[1]); obj.delete(); - Providers.reset(context); LSSnackBar(context: context, title: 'Renamed Profile', message: '"$old" has been renamed to "${_values[1]}"', type: SNACKBAR_TYPE.success); } } diff --git a/lib/modules/settings/modules/resources/route.dart b/lib/modules/settings/modules/resources/route.dart index b4251766..3192cbb4 100644 --- a/lib/modules/settings/modules/resources/route.dart +++ b/lib/modules/settings/modules/resources/route.dart @@ -3,25 +3,31 @@ import 'package:fluro_fork/fluro_fork.dart'; import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; -class SettingsResourcesRoute extends StatefulWidget { +class SettingsResourcesRouter { static const ROUTE_NAME = '/settings/resources'; + + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsResourcesRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsResourcesRoute()), transitionType: LunaRouter.transitionType, ); - SettingsResourcesRoute({ - Key key, - }): super(key: key); - - @override - State createState() => _State(); + SettingsResourcesRouter._(); } -class _State extends State { +class _SettingsResourcesRoute extends StatefulWidget { + @override + State<_SettingsResourcesRoute> createState() => _State(); +} + +class _State extends State<_SettingsResourcesRoute> { final GlobalKey _scaffoldKey = GlobalKey(); @override @@ -31,7 +37,11 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Resources'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', + title: 'Resources', + ); Widget get _body => LSListView( children: [ diff --git a/lib/modules/settings/modules/settings/route.dart b/lib/modules/settings/modules/settings/route.dart index de42a41b..dd8058e9 100644 --- a/lib/modules/settings/modules/settings/route.dart +++ b/lib/modules/settings/modules/settings/route.dart @@ -3,39 +3,61 @@ import 'package:fluro_fork/fluro_fork.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsRoute extends StatefulWidget { +class SettingsHomeRouter { static const ROUTE_NAME = '/settings'; + + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsRoute()), transitionType: LunaRouter.transitionType, ); - SettingsRoute({ - Key key, - }): super(key: key); - - @override - State createState() => _State(); + SettingsHomeRouter._(); } -class _State extends State { +class _SettingsRoute extends StatefulWidget { @override - Widget build(BuildContext context) => Scaffold( - key: Provider.of(context, listen: false).rootScaffoldKey, - appBar: _appBar, - drawer: _drawer, - body: _body, + State<_SettingsRoute> createState() => _State(); +} + +class _State extends State<_SettingsRoute> { + final GlobalKey _scaffoldKey = GlobalKey(); + + @override + Widget build(BuildContext context) => WillPopScope( + onWillPop: _onWillPop, + child: Scaffold( + key: _scaffoldKey, + appBar: _appBar, + drawer: _drawer, + body: _body, + ), ); + Future _onWillPop() async { + if(_scaffoldKey.currentState.isDrawerOpen) return true; + _scaffoldKey.currentState.openDrawer(); + return false; + } + Widget get _drawer => ValueListenableBuilder( valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.DRAWER_GROUP_MODULES.key]), builder: (context, box, _) => LSDrawer(page: SettingsConstants.MODULE_KEY), ); - Widget get _appBar => LSAppBar(title: SettingsConstants.MODULE_MAP.name); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: null, + hideLeading: true, + title: SettingsConstants.MODULE_MAP.name, + ); Widget get _body => LSListView( children: [ @@ -50,19 +72,19 @@ class _State extends State { title: LSTitle(text: 'Customization'), subtitle: LSSubtitle(text: 'Customize LunaSea & Modules'), trailing: LSIconButton(icon: Icons.brush), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsCustomizationRoute.ROUTE_NAME), + onTap: () async => SettingsCustomizationRouter.navigateTo(context), ), LSCardTile( title: LSTitle(text: 'Modules'), subtitle: LSSubtitle(text: 'Configure & Setup Modules'), trailing: LSIconButton(icon: Icons.device_hub), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsModulesRoute.ROUTE_NAME), + onTap: () async => SettingsModulesRouter.navigateTo(context), ), LSCardTile( title: LSTitle(text: 'Profiles'), subtitle: LSSubtitle(text: 'Manage Your Profiles'), trailing: LSIconButton(icon: Icons.person), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsProfilesRoute.ROUTE_NAME), + onTap: () async => SettingsProfilesRouter.navigateTo(context), ), ]; @@ -71,31 +93,31 @@ class _State extends State { title: LSTitle(text: 'Backup & Restore'), subtitle: LSSubtitle(text: 'Backup & Restore Your Configuration'), trailing: LSIconButton(icon: Icons.settings_backup_restore), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsBackupRestoreRoute.ROUTE_NAME), + onTap: () async => SettingsBackupRestoreRouter.navigateTo(context), ), LSCardTile( title: LSTitle(text: 'Donations'), subtitle: LSSubtitle(text: 'Donate to the Developer'), trailing: LSIconButton(icon: Icons.attach_money), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsDonationsRoute.ROUTE_NAME), + onTap: () async => SettingsDonationsRouter.navigateTo(context), ), LSCardTile( title: LSTitle(text: 'Logs'), subtitle: LSSubtitle(text: 'View, Export, & Clear Logs'), trailing: LSIconButton(icon: Icons.developer_mode), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsLogsRoute.ROUTE_NAME), + onTap: () async => SettingsLogsRouter.navigateTo(context), ), LSCardTile( title: LSTitle(text: 'Resources'), subtitle: LSSubtitle(text: 'Useful Resources & Links'), trailing: LSIconButton(icon: Icons.help_outline), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsResourcesRoute.ROUTE_NAME), + onTap: () async => SettingsResourcesRouter.navigateTo(context), ), LSCardTile( title: LSTitle(text: 'System'), subtitle: LSSubtitle(text: 'System Utilities & Information'), trailing: LSIconButton(icon: Icons.settings), - onTap: () async => SettingsRouter.router.navigateTo(context, SettingsSystemRoute.ROUTE_NAME), + onTap: () async => SettingsSystemRouter.navigateTo(context), ), ]; } diff --git a/lib/modules/settings/modules/system/route.dart b/lib/modules/settings/modules/system/route.dart index bc2c7500..c0b5ac59 100644 --- a/lib/modules/settings/modules/system/route.dart +++ b/lib/modules/settings/modules/system/route.dart @@ -3,25 +3,31 @@ import 'package:flutter/material.dart' hide Router; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/settings.dart'; -class SettingsSystemRoute extends StatefulWidget { +class SettingsSystemRouter { static const ROUTE_NAME = '/settings/system'; + + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + static String route() => ROUTE_NAME; - static void defineRoute(Router router) => router.define( + static void defineRoutes(Router router) => router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => SettingsSystemRoute()), + handler: Handler(handlerFunc: (context, params) => _SettingsSystemRoute()), transitionType: LunaRouter.transitionType, ); - - SettingsSystemRoute({ - Key key, - }): super(key: key); - @override - State createState() => _State(); + SettingsSystemRouter._(); } -class _State extends State with AutomaticKeepAliveClientMixin { +class _SettingsSystemRoute extends StatefulWidget { + @override + State<_SettingsSystemRoute> createState() => _State(); +} + +class _State extends State<_SettingsSystemRoute> with AutomaticKeepAliveClientMixin { final GlobalKey _scaffoldKey = GlobalKey(); @override @@ -37,7 +43,11 @@ class _State extends State with AutomaticKeepAliveClientMix ); } - Widget get _appBar => LSAppBar(title: 'System'); + Widget get _appBar => LunaAppBar( + context: context, + popUntil: '/settings', + title: 'System', + ); Widget get _body => LSListView( children: [ diff --git a/lib/modules/sonarr.dart b/lib/modules/sonarr.dart index 88cae875..4e70cd9d 100644 --- a/lib/modules/sonarr.dart +++ b/lib/modules/sonarr.dart @@ -1,3 +1,3 @@ +export 'package:sonarr/sonarr.dart'; export 'sonarr/core.dart'; -export 'sonarr/routes.dart'; -export 'sonarr/widgets.dart'; +export 'sonarr/modules.dart'; diff --git a/lib/modules/sonarr/core.dart b/lib/modules/sonarr/core.dart index 86fb2adc..823fb50e 100644 --- a/lib/modules/sonarr/core.dart +++ b/lib/modules/sonarr/core.dart @@ -1,6 +1,8 @@ -export 'core/api.dart'; export 'core/constants.dart'; export 'core/database.dart'; +export 'core/deprecated.dart'; export 'core/dialogs.dart'; -export 'core/state_global.dart'; -export 'core/sorting.dart'; +export 'core/extensions.dart'; +export 'core/router.dart'; +export 'core/state.dart'; +export 'core/types.dart'; diff --git a/lib/modules/sonarr/core/api.dart b/lib/modules/sonarr/core/api.dart deleted file mode 100644 index 1d50e48f..00000000 --- a/lib/modules/sonarr/core/api.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'api/api.dart'; -export 'api/data.dart'; diff --git a/lib/modules/sonarr/core/api/api.dart b/lib/modules/sonarr/core/api/api.dart deleted file mode 100644 index 6c66a591..00000000 --- a/lib/modules/sonarr/core/api/api.dart +++ /dev/null @@ -1,849 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; -import 'package:dio/adapter.dart'; -import 'package:dio/dio.dart'; -import 'package:intl/intl.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrAPI extends API { - final Map _values; - final Dio _dio; - - SonarrAPI._internal(this._values, this._dio); - factory SonarrAPI.from(ProfileHiveObject profile) { - Map _headers = Map.from(profile.getSonarr()['headers']); - Dio _client = Dio( - BaseOptions( - baseUrl: (profile.getSonarr()['host'] as String).endsWith('/') - ? '${profile.getSonarr()['host']}api/' - : '${profile.getSonarr()['host']}/api/', - queryParameters: { - if(profile.getSonarr()['key'] != '') 'apikey': profile.getSonarr()['key'], - }, - headers: _headers, - followRedirects: true, - maxRedirects: 5, - ), - ); - if(!profile.getSonarr()['strict_tls']) { - (_client.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) { - client.badCertificateCallback = (X509Certificate cert, String host, int port) => true; - }; - } - return SonarrAPI._internal( - profile.getSonarr(), - _client, - ); - } - - void logWarning(String methodName, String text) => Logger.warning('package:lunasea/core/api/sonarr/api.dart', methodName, 'Sonarr: $text'); - void logError(String methodName, String text, Object error, StackTrace trace, { - bool uploadToSentry = true, - }) => Logger.error( - 'package:lunasea/core/api/sonarr/api.dart', - methodName, - 'Sonarr: $text', - error, - trace, - uploadToSentry: uploadToSentry, - ); - - bool get enabled => _values['enabled']; - String get host => _values['host']; - String get key => _values['key']; - - Future testConnection() async { - try { - Response response = await _dio.get('system/status'); - if(response.statusCode == 200) return true; - } catch (error, stack) { - logError('testConnection', 'Connection test failed', error, stack, uploadToSentry: false); - } - return false; - } - - Future refreshSeries(int seriesID) async { - try { - await _dio.post( - 'command', - data: json.encode({ - 'name': 'RefreshSeries', - 'seriesId': seriesID, - }), - ); - return true; - } on DioError catch (error, stack) { - logError('refreshSeries', 'Failed to refresh series ($seriesID)', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('refreshSeries', 'Failed to refresh series ($seriesID)', error, stack); - return Future.error(error); - } - } - - Future removeSeries(int seriesID, { deleteFiles = false }) async { - try { - await _dio.delete( - 'series/$seriesID', - queryParameters: { - 'deleteFiles': deleteFiles, - }, - ); - return true; - } on DioError catch (error, stack) { - logError('removeSeries', 'Failed to remove series ($seriesID)', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('removeSeries', 'Failed to remove series ($seriesID)', error, stack); - return Future.error(error); - } - } - - Future> searchSeries(String search) async { - if(search == '') return []; - try { - Response response = await _dio.get( - 'series/lookup', - queryParameters: { - 'term': search, - } - ); - List entries = []; - for(var entry in response.data) { - entries.add(SonarrSearchData( - title: entry['title'] ?? 'Unknown Title', - overview: entry['overview'] == null || entry['overview'] == '' ? 'No summary is available.' : entry['overview'], - seasonCount: entry['seasonCount'] ?? 0, - status: entry['status'] ?? 'Unknown Status', - images: entry['images'] ?? [], - seasons: entry['seasons'] ?? [], - tvdbId: entry['tvdbId'] ?? 0, - tvMazeId: entry['tvMazeId'] ?? 0, - imdbId: entry['imdbId'] ?? '', - year: entry['year'] ?? 0, - )); - } - return entries; - } on DioError catch (error, stack) { - logError('searchSeries', 'Failed to search ($search)', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('searchSeries', 'Failed to search ($search)', error, stack); - return Future.error(error); - } - } - - Future addSeries( - SonarrSearchData entry, - SonarrQualityProfile qualityProfile, - SonarrRootFolder rootFolder, - SonarrSeriesType seriesType, - SonarrMonitorStatus monitorStatus, - bool seasonFolders, - bool monitored, - { bool search = false } - ) async { - monitorStatus.process(entry.seasons); - bool _ignoreWithFiles = - monitorStatus == SonarrMonitorStatus.MISSING || - monitorStatus == SonarrMonitorStatus.FUTURE; - bool _ignoreWithoutFiles = - monitorStatus == SonarrMonitorStatus.EXISTING || - monitorStatus == SonarrMonitorStatus.FUTURE; - try { - Response response = await _dio.post( - 'series', - data: json.encode({ - 'addOptions': { - 'ignoreEpisodesWithFiles': _ignoreWithFiles, - 'ignoreEpisodesWithoutFiles': _ignoreWithoutFiles, - 'searchForMissingEpisodes': search, - }, - 'tvdbId': entry.tvdbId, - 'title': entry.title, - 'titleSlug': entry.titleSlug, - 'profileId': qualityProfile.id, - 'images': entry.images, - 'seasons': entry.seasons, - 'rootFolderPath': rootFolder.path, - 'monitored': monitored, - 'seriesType': seriesType.type, - 'seasonFolder': seasonFolders, - }), - ); - return response.data['id']; - } on DioError catch (error, stack) { - logError('addSeries', 'Failed to add series (${entry.title})', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('addSeries', 'Failed to add series (${entry.title})', error, stack); - return Future.error(error); - } - } - - Future editSeries(int seriesID, SonarrQualityProfile qualityProfile, SonarrSeriesType seriesType, String path, bool monitored, bool seasonFolder) async { - try { - Response response = await _dio.get('series/$seriesID'); - Map series = response.data; - series['monitored'] = monitored; - series['seasonFolder'] = seasonFolder; - series['profileId'] = qualityProfile.id; - series['seriesType'] = seriesType.type; - series['path'] = path; - await _dio.put( - 'series', - data: json.encode(series), - ); - return true; - } on DioError catch (error, stack) { - logError('editSeries', 'Failed to edit series ($seriesID)', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('editSeries', 'Failed to edit series ($seriesID)', error, stack); - return Future.error(error); - } - } - - Future getSeries(int seriesID) async { - try { - Map _qualities = await getQualityProfiles().catchError((error) { return Future.error(error); }); - Response response = await _dio.get('series/$seriesID'); - Map body = response.data; - List _seasonData = body['seasons']; - _seasonData.sort((a, b) => a['seasonNumber'].compareTo(b['seasonNumber'])); - return SonarrCatalogueData( - title: body['title'] ?? 'Unknown Title', - sortTitle: body['sortTitle'] ?? 'Unknown Title', - seasonCount: body['seasonCount'] ?? 0, - seasonData: _seasonData ?? [], - episodeCount: body['episodeCount'] ?? 0, - episodeFileCount: body['episodeFileCount'] ?? 0, - status: body['status'] ?? 'Unknown Status', - seriesID: body['id'] ?? -1, - previousAiring: body['previousAiring'] ?? '', - nextAiring: body['nextAiring'] ?? '', - added: body['added'] ?? '', - network: body['network'] ?? 'Unknown Network', - monitored: body['monitored'] ?? false, - path: body['path'] ?? 'Unknown Path', - qualityProfile: body['qualityProfileId'] ?? 0, - type: body['seriesType'] ?? 'Unknown Series Type', - seasonFolder: body['seasonFolder'] ?? false, - overview: body['overview'] ?? 'No summary is available.', - tvdbId: body['tvdbId'] ?? 0, - tvMazeId: body['tvMazeId'] ?? 0, - imdbId: body['imdbId'] ?? '', - runtime: body['runtime'] ?? 0, - profile: body['profileId'] != null ? _qualities[body['qualityProfileId']].name : 'Unknown Quality Profile', - sizeOnDisk: body['sizeOnDisk'] ?? 0, - ); - } on DioError catch (error, stack) { - logError('getSeries', 'Failed to fetch series ($seriesID)', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('getSeries', 'Failed to fetch series ($seriesID)', error, stack); - return Future.error(error); - } - } - - Future> getAllSeries() async { - try { - Map _qualities = await getQualityProfiles().catchError((error) { return Future.error(error); }); - Response response = await _dio.get('series'); - List entries = []; - for(var entry in response.data) { - List _seasonData = entry['seasons']; - _seasonData.sort((a, b) => a['seasonNumber'].compareTo(b['seasonNumber'])); - entries.add(SonarrCatalogueData( - title: entry['title'] ?? 'Unknown Title', - sortTitle: entry['sortTitle'] ?? 'Unknown Title', - seasonCount: entry['seasonCount'] ?? 0, - seasonData: _seasonData ?? [], - episodeCount: entry['episodeCount'] ?? 0, - episodeFileCount: entry['episodeFileCount'] ?? 0, - status: entry['status'] ?? 'Unknown Status', - seriesID: entry['id'] ?? -1, - previousAiring: entry['previousAiring'] ?? '', - nextAiring: entry['nextAiring'] ?? '', - added: entry['added'] ?? '', - network: entry['network'] ?? 'Unknown Network', - monitored: entry['monitored'] ?? false, - path: entry['path'] ?? 'Unknown Path', - qualityProfile: entry['qualityProfileId'] ?? 0, - type: entry['seriesType'] ?? 'Unknown Series Type', - seasonFolder: entry['seasonFolder'] ?? false, - overview: entry['overview'] ?? 'No summary is available.', - tvdbId: entry['tvdbId'] ?? 0, - tvMazeId: entry['tvMazeId'] ?? 0, - imdbId: entry['imdbId'] ?? '', - runtime: entry['runtime'] ?? 0, - profile: entry['profileId'] != null ? _qualities[entry['qualityProfileId']].name : '', - sizeOnDisk: entry['sizeOnDisk'] ?? 0, - )); - } - return entries; - } on DioError catch (error, stack) { - logError('getAllSeries', 'Failed to fetch all series', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('getAllSeries', 'Failed to fetch all series', error, stack); - return Future.error(error); - } - } - - Future> getAllSeriesIDs() async { - try { - Response response = await _dio.get('series'); - List _entries = []; - for(var entry in response.data) _entries.add(entry['tvdbId'] ?? 0); - return _entries; - } on DioError catch (error, stack) { - logError('getAllSeriesIDs', 'Failed to fetch all series IDs', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('getAllSeriesIDs', 'Failed to fetch all series IDs', error, stack); - return Future.error(error); - } - } - - Future getUpcoming({int duration = 7}) async { - try { - DateTime now = DateTime.now(); - String start = DateFormat('y-MM-dd').format(now); - String end = DateFormat('y-MM-dd').format(now.add(Duration(days: duration))); - Response response = await _dio.get( - 'calendar', - queryParameters: { - 'start': start, - 'end': end, - } - ); - if(response.data.length <= 0) return {}; - Map _entries = {}; - for(int i=0; i> getHistory() async { - try { - Response response = await _dio.get( - 'history', - queryParameters: { - 'sortKey': 'date', - 'pageSize': 250, - 'sortDir': 'desc', - } - ); - List _entries = []; - for(var entry in response.data['records']) { - switch(entry['eventType']) { - case 'downloadFolderImported': { - _entries.add(SonarrHistoryDataDownloadImported( - seriesID: entry['seriesId'] ?? -1, - seriesTitle: entry['series']['title'] ?? 'Unknown Series Title', - episodeTitle: entry['episode']['title'] ?? 'Unknown Episode Title', - episodeNumber: entry['episode']['episodeNumber'] ?? 0, - seasonNumber: entry['episode']['seasonNumber'] ?? 0, - timestamp: entry['date'] ?? '', - quality: entry['quality']['quality']['name'] ?? '', - )); - break; - } - case 'downloadFailed': { - _entries.add(SonarrHistoryDataDownloadFailed( - seriesID: entry['seriesId'] ?? -1, - seriesTitle: entry['series']['title'] ?? 'Unknown Series Title', - episodeTitle: entry['episode']['title'] ?? 'Unknown Episode Title', - episodeNumber: entry['episode']['episodeNumber'] ?? 0, - seasonNumber: entry['episode']['seasonNumber'] ?? 0, - timestamp: entry['date'] ?? '', - )); - break; - } - case 'episodeFileDeleted': { - _entries.add(SonarrHistoryDataEpisodeDeleted( - seriesID: entry['seriesId'] ?? -1, - seriesTitle: entry['series']['title'] ?? 'Unknown Series Title', - episodeTitle: entry['episode']['title'] ?? 'Unknown Episode Title', - episodeNumber: entry['episode']['episodeNumber'] ?? 0, - seasonNumber: entry['episode']['seasonNumber'] ?? 0, - timestamp: entry['date'] ?? '', - reason: entry['data']['reason'] ?? 'Unknown Deletion Reason', - )); - break; - } - case 'episodeFileRenamed': { - _entries.add(SonarrHistoryDataEpisodeRenamed( - seriesID: entry['seriesId'] ?? -1, - seriesTitle: entry['series']['title'] ?? 'Unknown Series Title', - episodeTitle: entry['episode']['title'] ?? 'Unknown Episode Title', - episodeNumber: entry['episode']['episodeNumber'] ?? 0, - seasonNumber: entry['episode']['seasonNumber'] ?? 0, - timestamp: entry['date'] ?? '', - )); - break; - } - case 'grabbed': { - _entries.add(SonarrHistoryDataGrabbed( - seriesID: entry['seriesId'] ?? -1, - seriesTitle: entry['series']['title'] ?? 'Unknown Series Title', - episodeTitle: entry['episode']['title'] ?? 'Unknown Episode Title', - episodeNumber: entry['episode']['episodeNumber'] ?? 0, - seasonNumber: entry['episode']['seasonNumber'] ?? 0, - timestamp: entry['date'] ?? '', - indexer: entry['data']['indexer'] ?? 'Unknown Indexer', - )); - break; - } - default: { - _entries.add(SonarrHistoryDataGeneric( - seriesID: entry['seriesId'] ?? -1, - seriesTitle: entry['series']['title'] ?? 'Unknown Series Title', - episodeTitle: entry['episode']['title'] ?? 'Unknown Episode Title', - episodeNumber: entry['episode']['episodeNumber'] ?? 0, - seasonNumber: entry['episode']['seasonNumber'] ?? 0, - timestamp: entry['date'] ?? '', - eventType: entry['eventType'] ?? 'Unknown Event Type', - )); - break; - } - } - } - return _entries; - } on DioError catch (error, stack) { - logError('getHistory', 'Failed to fetch history', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('getHistory', 'Failed to fetch history', error, stack); - return Future.error(error); - } - } - - Future getEpisodes(int seriesID, int seasonNumber) async { - try { - Map _queue = await getQueue().catchError((error) { return Future.error(error); }); - Response response = await _dio.get( - 'episode', - queryParameters: { - 'seriesId': seriesID, - } - ); - Map entries = {}; - for(var entry in response.data) { - if(seasonNumber == -1 || entry['seasonNumber'] == seasonNumber) { - if(!entries.containsKey(entry['seasonNumber'])) { - entries[entry['seasonNumber']] = []; - entries[-1] = response.data.length; - } - String quality = ''; - bool cutoffMet = false; - int size = 0; - Map mediaInfo = {}; - SonarrQueueData _queueEntry; - if(entry['hasFile'] && entry['episodeFile'] != null) { - quality = entry['episodeFile']['quality']['quality']['name']; - cutoffMet = entry['episodeFile']['qualityCutoffNotMet']; - size = entry['episodeFile']['size']; - mediaInfo = entry['episodeFile']['mediaInfo']; - } - if(_queue.containsKey(entry['id'])) { - _queueEntry = _queue[entry['id']]; - } - entries[entry['seasonNumber']].add(SonarrEpisodeData( - episodeTitle: entry['title'] ?? 'Unknown Title', - seasonNumber: entry['seasonNumber'] ?? 0, - episodeNumber: entry['episodeNumber'] ?? 0, - overview: entry['overview'] ?? 'No summary is available.', - airDate: entry['airDateUtc'] ?? '', - episodeID: entry['id'] ?? -1, - episodeFileID: entry['episodeFileId'] ?? -1, - isMonitored: entry['monitored'] ?? false, - hasFile: entry['hasFile'] ?? false, - mediaInfo: mediaInfo ?? {}, - quality: quality ?? 'Unknown Quality', - cutoffNotMet: cutoffMet ?? false, - size: size ?? 0, - queue: _queueEntry ?? null, - )); - } - } - return entries; - } on DioError catch (error, stack) { - logError('getEpisodes', 'Failed to fetch episodes ($seriesID, $seasonNumber)', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('getEpisodes', 'Failed to fetch episodes ($seriesID, $seasonNumber)', error, stack); - return Future.error(error); - } - } - - Future getQueue() async { - try { - Response response = await _dio.get('queue'); - Map entries = {}; - for(var entry in response.data) { - if(entry['episode'] != null) entries[entry['episode']['id']] = SonarrQueueData( - episodeID: entry['episode']['id'] ?? 0, - size: entry['size'] ?? 0.0, - sizeLeft: entry['sizeleft'] ?? 0.0, - status: entry['status'] ?? 'Unknown Status', - releaseTitle: entry['title'] ?? 'Unknown Release', - seriesTitle: entry['series']['title'] ?? 'Unknown Series', - seasonNumber: entry['episode']['seasonNumber'] ?? -1, - episodeNumber: entry['episode']['episodeNumber'] ?? -1, - ); - } - return entries; - } on DioError catch (error, stack) { - logError('getQueue', 'Failed to fetch queue', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('getQueue', 'Failed to fetch queue', error, stack); - return Future.error(error); - } - } - - Future> getMissing() async { - try { - Response response = await _dio.get( - 'wanted/missing', - queryParameters: { - 'pageSize': 200, - } - ); - List entries = []; - for(var entry in response.data['records']) { - entries.add(SonarrMissingData( - showTitle: entry['series']['title'] ?? 'Unknown Series Title', - episodeTitle: entry['title'] ?? 'Unknown Episode Title', - seasonNumber: entry['seasonNumber'] ?? 0, - episodeNumber: entry['episodeNumber'] ?? 0, - airDateUTC: entry['airDateUtc'] ?? '', - seriesID: entry['series']['id'] ?? -1, - episodeID: entry['id'] ?? -1, - )); - } - return entries; - } on DioError catch (error, stack) { - logError('getMissing', 'Failed to fetch missing episodes', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('getMissing', 'Failed to fetch missing episodes', error, stack); - return Future.error(error); - } - } - - Future searchAllMissing() async { - try { - await _dio.post( - 'command', - data: json.encode({ - 'name': 'missingEpisodeSearch', - }), - ); - return true; - } on DioError catch (error, stack) { - logError('searchAllMissing', 'Failed to search for all missing episodes', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('searchAllMissing', 'Failed to search for all missing episodes', error, stack); - return Future.error(error); - } - } - - Future updateLibrary() async { - try { - await _dio.post( - 'command', - data: json.encode({ - 'name': 'refreshSeries', - }), - ); - return true; - } on DioError catch (error, stack) { - logError('updateLibrary', 'Failed to update library', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('updateLibrary', 'Failed to update library', error, stack); - return Future.error(error); - } - } - - Future triggerRssSync() async { - try { - await _dio.post( - 'command', - data: json.encode({ - 'name': 'RssSync', - }), - ); - return true; - } on DioError catch (error, stack) { - logError('triggerRssSync', 'Failed to trigger RSS sync', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('triggerRssSync', 'Failed to trigger RSS sync', error, stack); - return Future.error(error); - } - } - - Future triggerBackup() async { - try { - await _dio.post( - 'command', - data: json.encode({ - 'name': 'Backup', - }), - ); - return true; - } on DioError catch (error, stack) { - logError('triggerBackup', 'Failed to backup database', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('triggerBackup', 'Failed to backup database', error, stack); - return Future.error(error); - } - } - - Future searchSeason(int seriesID, int season) async { - try { - await _dio.post( - 'command', - data: json.encode({ - 'name': 'SeasonSearch', - 'seriesId': seriesID, - 'seasonNumber': season, - }), - ); - return true; - } on DioError catch (error, stack) { - logError('searchSeason', 'Failed to search for season ($seriesID, $season)', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('searchSeason', 'Failed to search for season ($seriesID, $season)', error, stack); - return Future.error(error); - } - } - - Future searchEpisodes(List episodeIDs) async { - try { - await _dio.post( - 'command', - data: json.encode({ - 'name': 'EpisodeSearch', - 'episodeIds': episodeIDs, - }), - ); - return true; - } on DioError catch (error, stack) { - logError('searchEpisodes', 'Failed to search for episodes (${episodeIDs.toString()})', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('searchEpisodes', 'Failed to search for episodes (${episodeIDs.toString()})', error, stack); - return Future.error(error); - } - } - - Future toggleSeriesMonitored(int seriesID, bool status) async { - try { - Response response = await _dio.get('series/$seriesID'); - Map body = response.data; - body['monitored'] = status; - await _dio.put( - 'series', - data: json.encode(body), - ); - return true; - } on DioError catch (error, stack) { - logError('toggleSeriesMonitored', 'Failed to toggle series monitored ($seriesID)', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('toggleSeriesMonitored', 'Failed to toggle series monitored ($seriesID)', error, stack); - return Future.error(error); - } - } - - Future toggleSeasonMonitored(int seriesID, int seasonID, bool status) async { - try { - Response response = await _dio.get('series/$seriesID'); - Map body = response.data; - for(var season in body['seasons']) { - if(season['seasonNumber'] == seasonID) { - season['monitored'] = status; - } - } - await _dio.put( - 'series', - data: json.encode(body), - ); - return true; - } on DioError catch (error, stack) { - logError('toggleSeasonMonitored', 'Failed to toggle season monitored ($seriesID, $seasonID)', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('toggleSeasonMonitored', 'Failed to toggle season monitored ($seriesID, $seasonID)', error, stack); - return Future.error(error); - } - } - - Future> getRootFolders() async { - try { - Response response = await _dio.get('rootfolder'); - List _entries = []; - for(var entry in response.data) { - _entries.add(SonarrRootFolder( - id: entry['id'] ?? -1, - path: entry['path'] ?? 'Unknown Root Folder', - freeSpace: entry['freeSpace'] ?? 0, - )); - } - return _entries; - } on DioError catch (error, stack) { - logError('getRootFolders', 'Failed to fetch root folders', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('getRootFolders', 'Failed to fetch root folders', error, stack); - return Future.error(error); - } - } - - Future> getQualityProfiles() async { - try { - Response response = await _dio.get('profile'); - var _entries = new Map(); - for(var entry in response.data) { - _entries[entry['id']] = SonarrQualityProfile( - id: entry['id'] ?? -1, - name: entry['name'] ?? 'Unknown Quality Profile', - ); - } - return _entries; - } on DioError catch (error, stack) { - logError('getQualityProfiles', 'Failed to fetch quality profiles', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('getQualityProfiles', 'Failed to fetch quality profiles', error, stack); - return Future.error(error); - } - } - - Future> getReleases(int episodeID) async { - try { - Response response = await _dio.get( - 'release', - queryParameters: { - 'episodeId': episodeID, - } - ); - List _entries = []; - for(var entry in response.data) { - _entries.add(SonarrReleaseData( - title: entry['title'] ?? 'Unknown Release Title', - guid: entry['guid'] ?? '', - quality: entry['quality']['quality']['name'] ?? 'Unknown', - protocol: entry['protocol'] ?? 'Unknown Protocol', - indexer: entry['indexer'] ?? 'Unknown Indexer', - infoUrl: entry['infoUrl'] ?? '', - approved: entry['approved'] ?? false, - releaseWeight: entry['releaseWeight'] ?? 0, - size: entry['size'] ?? 0, - indexerId: entry['indexerId'] ?? 0, - ageHours: entry['ageHours'] ?? 0, - rejections: entry['rejections'] ?? [], - seeders: entry['seeders'] ?? 0, - leechers: entry['leechers'] ?? 0, - )); - } - return _entries; - } on DioError catch (error, stack) { - logError('getReleases', 'Failed to fetch releases ($episodeID)', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('getReleases', 'Failed to fetch releases ($episodeID)', error, stack); - return Future.error(error); - } - } - - Future downloadRelease(String guid, int indexerId) async { - try { - await _dio.post( - 'release', - data: json.encode({ - 'guid': guid, - 'indexerId': indexerId, - }), - ); - return true; - } on DioError catch (error, stack) { - logError('downloadRelease', 'Failed to download release ($guid)', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('downloadRelease', 'Failed to download release ($guid)', error, stack); - return Future.error(error); - } - } - - Future toggleEpisodeMonitored(int episodeID, bool status) async { - try { - Response response = await _dio.get('episode/$episodeID'); - Map body = response.data; - body['monitored'] = status; - await _dio.put( - 'episode', - data: json.encode(body), - ); - return true; - } on DioError catch (error, stack) { - logError('toggleEpisodeMonitored', 'Failed to toggle episode monitored state ($episodeID, $status)', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('toggleEpisodeMonitored', 'Failed to toggle episode monitored state ($episodeID, $status)', error, stack); - return Future.error(error); - } - } - - Future deleteEpisodeFile(int episodeFileID) async { - try { - await _dio.delete('episodefile/$episodeFileID'); - return true; - } on DioError catch (error, stack) { - logError('deleteEpisodeFile', 'Failed to delete episode file ($episodeFileID)', error, stack, uploadToSentry: false); - return Future.error(error); - } catch (error, stack) { - logError('deleteEpisodeFile', 'Failed to delete episode file ($episodeFileID)', error, stack); - return Future.error(error); - } - } -} diff --git a/lib/modules/sonarr/core/api/data.dart b/lib/modules/sonarr/core/api/data.dart deleted file mode 100644 index da02b0b4..00000000 --- a/lib/modules/sonarr/core/api/data.dart +++ /dev/null @@ -1,12 +0,0 @@ -export 'data/catalogue.dart'; -export 'data/episode.dart'; -export 'data/history.dart'; -export 'data/missing.dart'; -export 'data/queue.dart'; -export 'data/qualityprofile.dart'; -export 'data/release.dart'; -export 'data/rootfolder.dart'; -export 'data/search.dart'; -export 'data/seriestype.dart'; -export 'data/upcoming.dart'; -export 'data/monitor_status.dart'; diff --git a/lib/modules/sonarr/core/api/data/catalogue.dart b/lib/modules/sonarr/core/api/data/catalogue.dart deleted file mode 100644 index 68b0968e..00000000 --- a/lib/modules/sonarr/core/api/data/catalogue.dart +++ /dev/null @@ -1,172 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrCatalogueData { - final Map api = Database.currentProfileObject.getSonarr(); - String title; - String sortTitle; - String status; - String previousAiring; - String nextAiring; - String added; - String network; - String overview; - String path; - String type; - List seasonData; - int qualityProfile; - int seasonCount; - int episodeCount; - int episodeFileCount; - int seriesID; - String profile; - bool seasonFolder; - bool monitored; - int tvdbId; - int tvMazeId; - String imdbId; - int runtime; - int sizeOnDisk; - - SonarrCatalogueData({ - @required this.title, - @required this.sortTitle, - @required this.seasonCount, - @required this.seasonData, - @required this.episodeCount, - @required this.episodeFileCount, - @required this.status, - @required this.seriesID, - @required this.previousAiring, - @required this.nextAiring, - @required this.added, - @required this.network, - @required this.monitored, - @required this.path, - @required this.qualityProfile, - @required this.type, - @required this.seasonFolder, - @required this.overview, - @required this.tvdbId, - @required this.tvMazeId, - @required this.imdbId, - @required this.runtime, - @required this.profile, - @required this.sizeOnDisk, - }); - - DateTime get nextAiringObject => DateTime.tryParse(nextAiring)?.toLocal(); - - DateTime get previousAiringObject => DateTime.tryParse(previousAiring)?.toLocal(); - - DateTime get dateAddedObject => DateTime.tryParse(added)?.toLocal(); - - String get seasonCountString { - return seasonCount == 1 ? '$seasonCount Season' : '$seasonCount Seasons'; - } - - String get dateAdded { - if(added != null) { - return DateFormat('MMMM dd, y').format(dateAddedObject); - } - return 'Unknown'; - } - - String get nextEpisode { - if(nextAiringObject != null) { - return DateFormat('MMMM dd, y').format(nextAiringObject); - } - return 'Unknown'; - } - - int get percentageComplete { - int _total = episodeCount ?? 0; - int _available = episodeFileCount ?? 0; - return _total == 0 - ? 0 - : ((_available/_total)*100).round(); - } - - String get airTimeString { - if(previousAiringObject != null) { - return LunaSeaDatabaseValue.USE_24_HOUR_TIME.data - ? DateFormat.Hm().format(previousAiringObject) - : DateFormat('KK:mm\na').format(previousAiringObject); - } - return 'Unknown'; - } - - String subtitle(SonarrCatalogueSorting sorting) { - if(previousAiringObject != null) { - if(network == null) { - return status == 'ended' ? - '$seasonCountString (Ended)\t•\t${_sortSubtitle(sorting)}\nAired on Unknown' : - '$seasonCountString\t•\t${_sortSubtitle(sorting)}\n${previousAiringObject.lsDateTime_time} on Unknown'; - } - return status == 'ended' ? - '$seasonCountString (Ended)\t•\t${_sortSubtitle(sorting)}\nAired on $network' : - '$seasonCountString\t•\t${_sortSubtitle(sorting)}\n${previousAiringObject.lsDateTime_time} on $network'; - } else { - if(network == null) { - return status == 'ended' ? - '$seasonCountString (Ended)\t•\t${_sortSubtitle(sorting)}\nAired on Unknown' : - '$seasonCountString\t•\t${_sortSubtitle(sorting)}\nAirs on Unknown'; - } - return status == 'ended' ? - '$seasonCountString (Ended)\t•\t${_sortSubtitle(sorting)}\nAired on $network' : - '$seasonCountString\t•\t${_sortSubtitle(sorting)}\nAirs on $network'; - } - } - - String _sortSubtitle(SonarrCatalogueSorting sorting) { - switch(sorting) { - case SonarrCatalogueSorting.type: return type.lsLanguage_Capitalize(); - case SonarrCatalogueSorting.quality: return profile; - case SonarrCatalogueSorting.episodes: return '${episodeFileCount ?? 0}/${episodeCount ?? 0} ($percentageComplete%)'; - case SonarrCatalogueSorting.nextAiring: return nextEpisode; - case SonarrCatalogueSorting.dateAdded: return dateAdded; - case SonarrCatalogueSorting.network: - case SonarrCatalogueSorting.size: - case SonarrCatalogueSorting.alphabetical: return sizeOnDisk?.lsBytes_BytesToString() ?? '0.0 B'; - } - return 'Unknown'; - } - - String posterURI({bool highRes = false}) { - if(api['enabled']) { - String _base = (api['host'] as String).endsWith('/') - ? '${api['host']}api/MediaCover' - : '${api['host']}/api/MediaCover'; - return highRes - ? '$_base/$seriesID/poster.jpg?apikey=${api['key']}' - : '$_base/$seriesID/poster-500.jpg?apikey=${api['key']}'; - } - return ''; - } - - String fanartURI({bool highRes = false}) { - if(api['enabled']) { - String _base = (api['host'] as String).endsWith('/') - ? '${api['host']}api/MediaCover' - : '${api['host']}/api/MediaCover'; - return highRes - ? '$_base/$seriesID/fanart.jpg?apikey=${api['key']}' - : '$_base/$seriesID/fanart-360.jpg?apikey=${api['key']}'; - } - return ''; - } - - String bannerURI({bool highRes = false}) { - if(api['enabled']) { - String _base = (api['host'] as String).endsWith('/') - ? '${api['host']}api/MediaCover' - : '${api['host']}/api/MediaCover'; - return highRes - ? '$_base/$seriesID/banner.jpg?apikey=${api['key']}' - : '$_base/$seriesID/banner-70.jpg?apikey=${api['key']}'; - } - return ''; - } -} \ No newline at end of file diff --git a/lib/modules/sonarr/core/api/data/episode.dart b/lib/modules/sonarr/core/api/data/episode.dart deleted file mode 100644 index 8b27c4b3..00000000 --- a/lib/modules/sonarr/core/api/data/episode.dart +++ /dev/null @@ -1,142 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:intl/intl.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrEpisodeData { - String episodeTitle; - String airDate; - String quality; - String overview; - int seasonNumber; - int episodeNumber; - int episodeID; - int episodeFileID; - int size; - bool isMonitored; - bool hasFile; - bool cutoffNotMet; - SonarrQueueData queue; - Map mediaInfo; - bool isSelected = false; - - SonarrEpisodeData({ - @required this.episodeTitle, - @required this.seasonNumber, - @required this.episodeNumber, - @required this.airDate, - @required this.episodeID, - @required this.episodeFileID, - @required this.isMonitored, - @required this.hasFile, - @required this.quality, - @required this.cutoffNotMet, - @required this.mediaInfo, - @required this.size, - @required this.queue, - @required this.overview, - }); - - String get sizeString { - return size?.lsBytes_BytesToString(); - } - - DateTime get airDateObject { - if(airDate != null) { - return DateTime.tryParse(airDate)?.toLocal(); - } - return null; - } - - String get airDateString { - if(airDateObject != null) { - return DateFormat('MMMM dd, y').format(airDateObject); - } - return 'Unknown Air Date'; - } - - bool get hasAired { - if(airDateObject == null) { - return false; - } - return airDateObject.isBefore(DateTime.now()); - } - - dynamic subtitle({ bool asHighlight = false }) { - if(queue != null) { - //Get Queue status - String _queueStatus = ''; - try { - _queueStatus = ' (${(100-((queue?.sizeLeft ?? 0)/(queue?.size ?? 1))*100).abs().toInt()}%)'; - } catch(_) { - Logger.warning('SonarrEpisodeData', 'subtitle', 'Failed to parse queue status (${queue?.sizeLeft}, ${queue?.size}'); - } - return asHighlight - ? LSTextHighlighted( - text: '${queue?.status ?? 'Unknown'}$_queueStatus', - bgColor: Colors.blue, - ) - : TextSpan( - text: '${queue?.status ?? 'Unknown'}$_queueStatus', - style: TextStyle( - color: Colors.blue, - fontWeight: FontWeight.bold, - ), - ); - } - if(hasFile) { - if(cutoffNotMet) { - return asHighlight - ? LSTextHighlighted( - text: '$quality - $sizeString', - bgColor: LSColors.orange, - ) - : TextSpan( - text: '$quality - $sizeString', - style: TextStyle( - color: isMonitored ? Colors.orange : Colors.orange.withOpacity(0.30), - fontWeight: FontWeight.bold, - ), - ); - } - return asHighlight - ? LSTextHighlighted( - text: '$quality - $sizeString', - bgColor: LSColors.accent, - ) - : TextSpan( - text: '$quality - $sizeString', - style: TextStyle( - color: isMonitored ? Color(Constants.ACCENT_COLOR) : Color(Constants.ACCENT_COLOR).withOpacity(0.30), - fontWeight: FontWeight.bold, - ), - ); - } - if(hasAired) { - return asHighlight - ? LSTextHighlighted( - text: 'Missing', - bgColor: LSColors.red, - ) - : TextSpan( - text: 'Missing', - style: TextStyle( - color: isMonitored ? Colors.red : Colors.red.withOpacity(0.30), - fontWeight: FontWeight.bold, - ), - ); - } - return asHighlight - ? LSTextHighlighted( - text: 'Unaired', - bgColor: LSColors.blue, - ) - : TextSpan( - text: 'Unaired', - style: TextStyle( - color: isMonitored ? Colors.blue : Colors.blue.withOpacity(0.30), - fontWeight: FontWeight.bold, - ), - ); - } -} diff --git a/lib/modules/sonarr/core/api/data/history.dart b/lib/modules/sonarr/core/api/data/history.dart deleted file mode 100644 index cc2bed18..00000000 --- a/lib/modules/sonarr/core/api/data/history.dart +++ /dev/null @@ -1,229 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -abstract class SonarrHistoryData { - String seriesTitle; - String episodeTitle; - String timestamp; - String eventType; - int seriesID; - int episodeNumber; - int seasonNumber; - - SonarrHistoryData( - this.seriesID, - this.seriesTitle, - this.episodeTitle, - this.episodeNumber, - this.seasonNumber, - this.timestamp, - this.eventType, - ); - - DateTime get timestampObject { - return DateTime.tryParse(timestamp)?.toLocal(); - } - - String get timestampString { - if(timestampObject != null) { - Duration age = DateTime.now().difference(timestampObject); - if(age.inDays >= 1) { - return age.inDays == 1 ? '${age.inDays} Day Ago' : '${age.inDays} Days Ago'; - } - if(age.inHours >= 1) { - return age.inHours == 1 ? '${age.inHours} Hour Ago' : '${age.inHours} Hours Ago'; - } - return age.inMinutes == 1 ? '${age.inMinutes} Minute Ago' : '${age.inMinutes} Minutes Ago'; - } - return 'Unknown Date/Time'; - } - - List get subtitle; -} - -class SonarrHistoryDataGeneric extends SonarrHistoryData { - String eventType; - - SonarrHistoryDataGeneric({ - @required int seriesID, - @required String seriesTitle, - @required String episodeTitle, - @required int episodeNumber, - @required int seasonNumber, - @required String timestamp, - @required this.eventType, - }) : super(seriesID, seriesTitle, episodeTitle, episodeNumber, seasonNumber, timestamp, eventType); - - List get subtitle { - return [ - TextSpan( - text: 'Season $seasonNumber Episode $episodeNumber\n', - ), - TextSpan( - text: '$timestampString\n', - ), - TextSpan( - text: '$eventType', - style: TextStyle( - color: LSColors.purple, - fontWeight: FontWeight.bold, - ), - ), - ]; - } -} - -class SonarrHistoryDataEpisodeRenamed extends SonarrHistoryData { - SonarrHistoryDataEpisodeRenamed({ - @required int seriesID, - @required String seriesTitle, - @required String episodeTitle, - @required int episodeNumber, - @required int seasonNumber, - @required String timestamp, - }) : super(seriesID, seriesTitle, episodeTitle, episodeNumber, seasonNumber, timestamp, 'episodeFileRenamed'); - - List get subtitle { - return [ - TextSpan( - text: 'Season $seasonNumber Episode $episodeNumber\n', - ), - TextSpan( - text: '$timestampString\n', - ), - TextSpan( - text: '${SonarrConstants.EVENT_TYPE_MESSAGES[eventType]}', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - fontWeight: FontWeight.bold, - ), - ), - ]; - } -} - -class SonarrHistoryDataEpisodeDeleted extends SonarrHistoryData { - String reason; - - SonarrHistoryDataEpisodeDeleted ({ - @required int seriesID, - @required String seriesTitle, - @required String episodeTitle, - @required int episodeNumber, - @required int seasonNumber, - @required String timestamp, - @required this.reason, - }) : super(seriesID, seriesTitle, episodeTitle, episodeNumber, seasonNumber, timestamp, 'episodeFileDeleted'); - - List get subtitle { - return [ - TextSpan( - text: 'Season $seasonNumber Episode $episodeNumber\n', - ), - TextSpan( - text: '$timestampString\n', - ), - TextSpan( - text: '${SonarrConstants.EVENT_TYPE_MESSAGES[eventType]} (${Constants.historyReasonMessages[reason] ?? reason})', - style: TextStyle( - color: Colors.red, - fontWeight: FontWeight.bold, - ), - ), - ]; - } -} - -class SonarrHistoryDataDownloadImported extends SonarrHistoryData { - String quality; - - SonarrHistoryDataDownloadImported ({ - @required int seriesID, - @required String seriesTitle, - @required String episodeTitle, - @required int episodeNumber, - @required int seasonNumber, - @required String timestamp, - @required this.quality, - }) : super(seriesID, seriesTitle, episodeTitle, episodeNumber, seasonNumber, timestamp, 'downloadFolderImported'); - - List get subtitle { - return [ - TextSpan( - text: 'Season $seasonNumber Episode $episodeNumber\n', - ), - TextSpan( - text: '$timestampString\n', - ), - TextSpan( - text: '${SonarrConstants.EVENT_TYPE_MESSAGES[eventType]} ($quality)', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - fontWeight: FontWeight.bold, - ), - ), - ]; - } -} - -class SonarrHistoryDataDownloadFailed extends SonarrHistoryData { - SonarrHistoryDataDownloadFailed({ - @required int seriesID, - @required String seriesTitle, - @required String episodeTitle, - @required int episodeNumber, - @required int seasonNumber, - @required String timestamp, - }) : super(seriesID, seriesTitle, episodeTitle, episodeNumber, seasonNumber, timestamp, 'downloadFailed'); - - List get subtitle { - return [ - TextSpan( - text: 'Season $seasonNumber Episode $episodeNumber\n', - ), - TextSpan( - text: '$timestampString\n', - ), - TextSpan( - text: '${SonarrConstants.EVENT_TYPE_MESSAGES[eventType]}', - style: TextStyle( - color: Colors.red, - fontWeight: FontWeight.bold, - ), - ), - ]; - } -} - -class SonarrHistoryDataGrabbed extends SonarrHistoryData { - String indexer; - - SonarrHistoryDataGrabbed ({ - @required int seriesID, - @required String seriesTitle, - @required String episodeTitle, - @required int episodeNumber, - @required int seasonNumber, - @required String timestamp, - @required this.indexer, - }) : super(seriesID, seriesTitle, episodeTitle, episodeNumber, seasonNumber, timestamp, 'grabbed'); - - List get subtitle { - return [ - TextSpan( - text: 'Season $seasonNumber Episode $episodeNumber\n', - ), - TextSpan( - text: '$timestampString\n', - ), - TextSpan( - text: '${SonarrConstants.EVENT_TYPE_MESSAGES[eventType]} $indexer', - style: TextStyle( - color: Colors.orange, - fontWeight: FontWeight.bold, - ), - ), - ]; - } -} diff --git a/lib/modules/sonarr/core/api/data/missing.dart b/lib/modules/sonarr/core/api/data/missing.dart deleted file mode 100644 index 47028695..00000000 --- a/lib/modules/sonarr/core/api/data/missing.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core/database.dart'; - -class SonarrMissingData { - final Map api = Database.currentProfileObject.getSonarr(); - String showTitle; - String episodeTitle; - String airDateUTC; - int seasonNumber; - int episodeNumber; - int seriesID; - int episodeID; - - SonarrMissingData({ - @required this.showTitle, - @required this.episodeTitle, - @required this.seasonNumber, - @required this.episodeNumber, - @required this.airDateUTC, - @required this.seriesID, - @required this.episodeID, - }); - - DateTime get airDateObject { - return DateTime.tryParse(airDateUTC)?.toLocal(); - } - - String get seasonEpisode { - return 'Season $seasonNumber Episode $episodeNumber'; - } - - String get airDateString { - if(airDateObject != null) { - Duration age = DateTime.now().difference(airDateObject); - if(age.inDays >= 1) { - return age.inDays <= 1 ? '${age.inDays} Day Ago' : '${age.inDays} Days Ago'; - } - if(age.inHours >= 1) { - return age.inHours <= 1 ? '${age.inHours} Hour Ago' : '${age.inHours} Hours Ago'; - } - return age.inMinutes <= 1 ? '${age.inMinutes} Minute Ago' : '${age.inMinutes} Minutes Ago'; - } - return 'Unknown Date/Time'; - } - - String posterURI({bool highRes = false}) { - if(api['enabled']) { - String _base = (api['host'] as String).endsWith('/') - ? '${api['host']}api/MediaCover' - : '${api['host']}/api/MediaCover'; - return highRes - ? '$_base/$seriesID/poster.jpg?apikey=${api['key']}' - : '$_base/$seriesID/poster-500.jpg?apikey=${api['key']}'; - } - return ''; - } - - String fanartURI({bool highRes = false}) { - if(api['enabled']) { - String _base = (api['host'] as String).endsWith('/') - ? '${api['host']}api/MediaCover' - : '${api['host']}/api/MediaCover'; - return highRes - ? '$_base/$seriesID/fanart.jpg?apikey=${api['key']}' - : '$_base/$seriesID/fanart-360.jpg?apikey=${api['key']}'; - } - return ''; - } - - String bannerURI({bool highRes = false}) { - if(api['enabled']) { - String _base = (api['host'] as String).endsWith('/') - ? '${api['host']}api/MediaCover' - : '${api['host']}/api/MediaCover'; - return highRes - ? '$_base/$seriesID/banner.jpg?apikey=${api['key']}' - : '$_base/$seriesID/banner-70.jpg?apikey=${api['key']}'; - } - return ''; - } -} \ No newline at end of file diff --git a/lib/modules/sonarr/core/api/data/monitor_status.dart b/lib/modules/sonarr/core/api/data/monitor_status.dart deleted file mode 100644 index 30fd74a8..00000000 --- a/lib/modules/sonarr/core/api/data/monitor_status.dart +++ /dev/null @@ -1,71 +0,0 @@ -import 'package:lunasea/core.dart'; -part 'monitor_status.g.dart'; - -@HiveType(typeId: 14, adapterName: 'SonarrMonitorStatusAdapter') -enum SonarrMonitorStatus { - @HiveField(0) - ALL, - @HiveField(1) - FUTURE, - @HiveField(2) - MISSING, - @HiveField(3) - EXISTING, - @HiveField(4) - FIRST_SEASON, - @HiveField(5) - LAST_SEASON, - @HiveField(6) - NONE, -} - -extension SonarrMonitorStatusExtension on SonarrMonitorStatus { - String get name { - switch(this) { - case SonarrMonitorStatus.ALL: return 'All'; - case SonarrMonitorStatus.MISSING: return 'Missing'; - case SonarrMonitorStatus.EXISTING: return 'Existing'; - case SonarrMonitorStatus.FIRST_SEASON: return 'First Season'; - case SonarrMonitorStatus.LAST_SEASON: return 'Last Season'; - case SonarrMonitorStatus.NONE: return 'None'; - case SonarrMonitorStatus.FUTURE: return 'Future'; - } - throw Exception('unknown name'); - } - - void process(List data) { - switch(this) { - case SonarrMonitorStatus.ALL: _all(data); break; - case SonarrMonitorStatus.MISSING: _missing(data); break; - case SonarrMonitorStatus.EXISTING: _existing(data); break; - case SonarrMonitorStatus.FIRST_SEASON: _firstSeason(data); break; - case SonarrMonitorStatus.LAST_SEASON: _lastSeason(data); break; - case SonarrMonitorStatus.NONE: _none(data); break; - case SonarrMonitorStatus.FUTURE: _future(data); break; - } - } - - void _all(List data) => data.forEach((element) { - if(element['seasonNumber'] != 0) element['monitored'] = true; - }); - - void _missing(List data) => _all(data); - - void _existing(List data) => _all(data); - - void _future(List data) => _lastSeason(data); - - void _firstSeason(List data) { - _none(data); - data[0]['seasonNumber'] == 0 - ? data[1]['monitored'] = true - : data[0]['monitored'] = false; - } - - void _lastSeason(List data) { - _none(data); - data[data.length-1]['monitored'] = true; - } - - void _none(List data) => data.forEach((element) => element['monitored'] = false); -} diff --git a/lib/modules/sonarr/core/api/data/queue.dart b/lib/modules/sonarr/core/api/data/queue.dart deleted file mode 100644 index 03daa331..00000000 --- a/lib/modules/sonarr/core/api/data/queue.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter/material.dart'; - -class SonarrQueueData { - int episodeID; - double size; - double sizeLeft; - String status; - String seriesTitle; - String releaseTitle; - int seasonNumber; - int episodeNumber; - - SonarrQueueData({ - @required this.episodeID, - @required this.size, - @required this.sizeLeft, - @required this.status, - @required this.seriesTitle, - @required this.releaseTitle, - @required this.seasonNumber, - @required this.episodeNumber, - }); -} diff --git a/lib/modules/sonarr/core/api/data/release.dart b/lib/modules/sonarr/core/api/data/release.dart deleted file mode 100644 index 55fccc68..00000000 --- a/lib/modules/sonarr/core/api/data/release.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:flutter/material.dart'; - -class SonarrReleaseData { - String title; - String guid; - String quality; - String protocol; - String indexer; - String infoUrl; - bool approved; - int releaseWeight; - int size; - int indexerId; - int seeders; - int leechers; - double ageHours; - List rejections; - - SonarrReleaseData({ - @required this.title, - @required this.guid, - @required this.quality, - @required this.protocol, - @required this.indexer, - @required this.infoUrl, - @required this.approved, - @required this.releaseWeight, - @required this.size, - @required this.indexerId, - @required this.ageHours, - @required this.rejections, - @required this.seeders, - @required this.leechers, - }); - - bool get isTorrent { - return protocol == 'torrent'; - } -} \ No newline at end of file diff --git a/lib/modules/sonarr/core/api/data/search.dart b/lib/modules/sonarr/core/api/data/search.dart deleted file mode 100644 index 22ade4ff..00000000 --- a/lib/modules/sonarr/core/api/data/search.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; - -class SonarrSearchData { - String title; - String overview; - int seasonCount; - int tvdbId; - int tvMazeId; - int year; - String imdbId; - String status; - List images; - List seasons; - - SonarrSearchData({ - @required this.title, - @required this.overview, - @required this.seasonCount, - @required this.status, - @required this.images, - @required this.seasons, - @required this.tvdbId, - @required this.tvMazeId, - @required this.imdbId, - @required this.year, - }); - - String get titleSlug { - return title.lsSlugs_ConvertToSlug(); - } - - String get seasonCountString { - return seasonCount == 1 ? '$seasonCount Season' : '$seasonCount Seasons'; - } - - String get bannerURI { - for(var image in images) { - if(image['coverType'] == 'banner') { - return image['url']; - } - } - return ''; - } - - String get fanartURI { - for(var image in images) { - if(image['coverType'] == 'fanart') { - return image['url']; - } - } - return ''; - } - - String get posterURI { - for(var image in images) { - if(image['coverType'] == 'poster') { - return image['url']; - } - } - return ''; - } -} \ No newline at end of file diff --git a/lib/modules/sonarr/core/api/data/seriestype.dart b/lib/modules/sonarr/core/api/data/seriestype.dart deleted file mode 100644 index bf697c9b..00000000 --- a/lib/modules/sonarr/core/api/data/seriestype.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; - -part 'seriestype.g.dart'; - -@HiveType(typeId: 4, adapterName: 'SonarrSeriesTypeAdapter') -class SonarrSeriesType extends HiveObject { - @HiveField(0) - String type; - - factory SonarrSeriesType.empty() => SonarrSeriesType( - type: '', - ); - - SonarrSeriesType({ - @required this.type, - }); -} diff --git a/lib/modules/sonarr/core/api/data/upcoming.dart b/lib/modules/sonarr/core/api/data/upcoming.dart deleted file mode 100644 index 16607e9e..00000000 --- a/lib/modules/sonarr/core/api/data/upcoming.dart +++ /dev/null @@ -1,100 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'package:lunasea/core.dart'; - -class SonarrUpcomingData { - final Map api = Database.currentProfileObject.getSonarr(); - String seriesTitle; - String episodeTitle; - int seasonNumber; - int episodeNumber; - int seriesID; - int id; - String airTime; - String filePath; - bool hasFile; - - SonarrUpcomingData({ - @required this.seriesTitle, - @required this.episodeTitle, - @required this.seasonNumber, - @required this.episodeNumber, - @required this.seriesID, - @required this.id, - @required this.airTime, - @required this.hasFile, - @required this.filePath, - }); - - DateTime get airTimeObject { - return DateTime.tryParse(airTime)?.toLocal(); - } - - String get airTimeString { - if(airTimeObject != null) { - return LunaSeaDatabaseValue.USE_24_HOUR_TIME.data - ? DateFormat.Hm().format(airTimeObject) - : DateFormat('KK:mm\na').format(airTimeObject); - } - return 'N/A'; - } - - String posterURI({bool highRes = false}) { - if(api['enabled']) { - String _base = (api['host'] as String).endsWith('/') - ? '${api['host']}api/MediaCover' - : '${api['host']}/api/MediaCover'; - return highRes - ? '$_base/$seriesID/poster.jpg?apikey=${api['key']}' - : '$_base/$seriesID/poster-500.jpg?apikey=${api['key']}'; - } - return ''; - } - - String fanartURI({bool highRes = false}) { - if(api['enabled']) { - String _base = (api['host'] as String).endsWith('/') - ? '${api['host']}api/MediaCover' - : '${api['host']}/api/MediaCover'; - return highRes - ? '$_base/$seriesID/fanart.jpg?apikey=${api['key']}' - : '$_base/$seriesID/fanart-360.jpg?apikey=${api['key']}'; - } - return ''; - } - - String bannerURI({bool highRes = false}) { - if(api['enabled']) { - String _base = (api['host'] as String).endsWith('/') - ? '${api['host']}api/MediaCover' - : '${api['host']}/api/MediaCover'; - return highRes - ? '$_base/$seriesID/banner.jpg?apikey=${api['key']}' - : '$_base/$seriesID/banner-70.jpg?apikey=${api['key']}'; - } - return ''; - } - - String get seasonEpisode { - return 'Season $seasonNumber Episode $episodeNumber'; - } - - TextSpan get downloaded { - if(hasFile) { - return TextSpan( - text: 'Downloaded ($filePath)', - style: TextStyle( - color: Color(Constants.ACCENT_COLOR), - fontWeight: FontWeight.bold, - ), - ); - } - return TextSpan( - text: 'Not Downloaded', - style: TextStyle( - color: Colors.red, - fontWeight: FontWeight.bold, - ), - ); - } -} \ No newline at end of file diff --git a/lib/modules/sonarr/core/constants.dart b/lib/modules/sonarr/core/constants.dart index 12bd19fa..957b13af 100644 --- a/lib/modules/sonarr/core/constants.dart +++ b/lib/modules/sonarr/core/constants.dart @@ -1,13 +1,12 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; class SonarrConstants { SonarrConstants._(); static const String MODULE_KEY = 'sonarr'; - static const ModuleMap MODULE_MAP = ModuleMap( + static const LunaModuleMap MODULE_MAP = LunaModuleMap( name: 'Sonarr', description: 'Manage Television Series', settingsDescription: 'Configure Sonarr', @@ -21,19 +20,4 @@ class SonarrConstants { type: MODULE_KEY, localizedTitle: MODULE_MAP.name, ); - - static const Map EVENT_TYPE_MESSAGES = { - 'episodeFileRenamed': 'Episode File Renamed', - 'episodeFileDeleted': 'Episode File Deleted', - 'downloadFolderImported': 'Imported Episode File', - 'downloadFailed': 'Download Failed', - 'grabbed': 'Grabbed From', - }; - - // ignore: non_constant_identifier_names - static final List SERIES_TYPES = [ - SonarrSeriesType(type: 'anime'), - SonarrSeriesType(type: 'daily'), - SonarrSeriesType(type: 'standard'), - ]; -} \ No newline at end of file +} diff --git a/lib/modules/sonarr/core/database.dart b/lib/modules/sonarr/core/database.dart index 793d9778..0c7e4980 100644 --- a/lib/modules/sonarr/core/database.dart +++ b/lib/modules/sonarr/core/database.dart @@ -1,38 +1,49 @@ -import 'package:hive/hive.dart'; import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart' hide SonarrDatabaseValueExtension; +import 'package:lunasea/modules/sonarr.dart'; class SonarrDatabase { SonarrDatabase._(); static void registerAdapters() { - Hive.registerAdapter(SonarrQualityProfileAdapter()); - Hive.registerAdapter(SonarrRootFolderAdapter()); - Hive.registerAdapter(SonarrSeriesTypeAdapter()); + // Deprecated, not in use but necessary to avoid Hive read errors + Hive.registerAdapter(DeprecatedSonarrQualityProfileAdapter()); + Hive.registerAdapter(DeprecatedSonarrRootFolderAdapter()); + Hive.registerAdapter(DeprecatedSonarrSeriesTypeAdapter()); + // Active adapters Hive.registerAdapter(SonarrMonitorStatusAdapter()); } } enum SonarrDatabaseValue { NAVIGATION_INDEX, - ADD_MONITORED, - ADD_SEASON_FOLDERS, - ADD_QUALITY_PROFILE, - ADD_ROOT_FOLDER, - ADD_SERIES_TYPE, - ADD_MONITOR_STATUS, + NAVIGATION_INDEX_SERIES_DETAILS, + ADD_SERIES_DEFAULT_MONITORED, + ADD_SERIES_DEFAULT_USE_SEASON_FOLDERS, + ADD_SERIES_DEFAULT_SERIES_TYPE, + ADD_SERIES_DEFAULT_MONITOR_STATUS, + ADD_SERIES_DEFAULT_LANGUAGE_PROFILE, + ADD_SERIES_DEFAULT_QUALITY_PROFILE, + ADD_SERIES_DEFAULT_ROOT_FOLDER, + UPCOMING_FUTURE_DAYS, + QUEUE_REFRESH_RATE, + CONTENT_LOAD_LENGTH, } extension SonarrDatabaseValueExtension on SonarrDatabaseValue { String get key { switch(this) { case SonarrDatabaseValue.NAVIGATION_INDEX: return 'SONARR_NAVIGATION_INDEX'; - case SonarrDatabaseValue.ADD_MONITORED: return 'SONARR_ADD_MONITORED'; - case SonarrDatabaseValue.ADD_SEASON_FOLDERS: return 'SONARR_ADD_SEASON_FOLDERS'; - case SonarrDatabaseValue.ADD_QUALITY_PROFILE: return 'SONARR_ADD_QUALITY_PROFILE'; - case SonarrDatabaseValue.ADD_ROOT_FOLDER: return 'SONARR_ADD_ROOT_FOLDER'; - case SonarrDatabaseValue.ADD_SERIES_TYPE: return 'SONARR_ADD_SERIES_TYPE'; - case SonarrDatabaseValue.ADD_MONITOR_STATUS: return 'SONARR_ADD_MONITOR_STATUS'; + case SonarrDatabaseValue.NAVIGATION_INDEX_SERIES_DETAILS: return 'SONARR_NAVIGATION_INDEX_SERIES_DETAILS'; + case SonarrDatabaseValue.UPCOMING_FUTURE_DAYS: return 'SONARR_UPCOMING_FUTURE_DAYS'; + case SonarrDatabaseValue.QUEUE_REFRESH_RATE: return 'SONARR_QUEUE_REFRESH_RATE'; + case SonarrDatabaseValue.CONTENT_LOAD_LENGTH: return 'SONARR_CONTENT_LOAD_LENGTH'; + case SonarrDatabaseValue.ADD_SERIES_DEFAULT_MONITORED: return 'SONARR_ADD_SERIES_DEFAULT_MONITORED'; + case SonarrDatabaseValue.ADD_SERIES_DEFAULT_USE_SEASON_FOLDERS: return 'SONARR_ADD_SERIES_DEFAULT_USE_SEASON_FOLDERS'; + case SonarrDatabaseValue.ADD_SERIES_DEFAULT_SERIES_TYPE: return 'SONARR_ADD_SERIES_DEFAULT_SERIES_TYPE'; + case SonarrDatabaseValue.ADD_SERIES_DEFAULT_MONITOR_STATUS: return 'SONARR_ADD_SERIES_DEFAULT_MONITOR_STATUS'; + case SonarrDatabaseValue.ADD_SERIES_DEFAULT_LANGUAGE_PROFILE: return 'SONARR_ADD_SERIES_DEFAULT_LANGUAGE_PROFILE'; + case SonarrDatabaseValue.ADD_SERIES_DEFAULT_QUALITY_PROFILE: return 'SONARR_ADD_SERIES_DEFAULT_QUALITY_PROFILE'; + case SonarrDatabaseValue.ADD_SERIES_DEFAULT_ROOT_FOLDER: return 'SONARR_ADD_SERIES_DEFAULT_ROOT_FOLDER'; } throw Exception('key not found'); } @@ -41,12 +52,17 @@ extension SonarrDatabaseValueExtension on SonarrDatabaseValue { final _box = Database.lunaSeaBox; switch(this) { case SonarrDatabaseValue.NAVIGATION_INDEX: return _box.get(this.key, defaultValue: 0); - case SonarrDatabaseValue.ADD_MONITORED: return _box.get(this.key, defaultValue: true); - case SonarrDatabaseValue.ADD_SEASON_FOLDERS: return _box.get(this.key, defaultValue: true); - case SonarrDatabaseValue.ADD_QUALITY_PROFILE: return _box.get(this.key); - case SonarrDatabaseValue.ADD_ROOT_FOLDER: return _box.get(this.key); - case SonarrDatabaseValue.ADD_SERIES_TYPE: return _box.get(this.key); - case SonarrDatabaseValue.ADD_MONITOR_STATUS: return _box.get(this.key); + case SonarrDatabaseValue.NAVIGATION_INDEX_SERIES_DETAILS: return _box.get(this.key, defaultValue: 0); + case SonarrDatabaseValue.UPCOMING_FUTURE_DAYS: return _box.get(this.key, defaultValue: 7); + case SonarrDatabaseValue.QUEUE_REFRESH_RATE: return _box.get(this.key, defaultValue: 60); + case SonarrDatabaseValue.CONTENT_LOAD_LENGTH: return _box.get(this.key, defaultValue: 125); + case SonarrDatabaseValue.ADD_SERIES_DEFAULT_MONITORED: return _box.get(this.key, defaultValue: true); + case SonarrDatabaseValue.ADD_SERIES_DEFAULT_USE_SEASON_FOLDERS: return _box.get(this.key, defaultValue: true); + case SonarrDatabaseValue.ADD_SERIES_DEFAULT_SERIES_TYPE: return _box.get(this.key, defaultValue: 'standard'); + case SonarrDatabaseValue.ADD_SERIES_DEFAULT_MONITOR_STATUS: return _box.get(this.key, defaultValue: SonarrMonitorStatus.ALL); + case SonarrDatabaseValue.ADD_SERIES_DEFAULT_LANGUAGE_PROFILE : return _box.get(this.key, defaultValue: null); + case SonarrDatabaseValue.ADD_SERIES_DEFAULT_QUALITY_PROFILE : return _box.get(this.key, defaultValue: null); + case SonarrDatabaseValue.ADD_SERIES_DEFAULT_ROOT_FOLDER : return _box.get(this.key, defaultValue: null); } throw Exception('data not found'); } diff --git a/lib/modules/sonarr/core/deprecated.dart b/lib/modules/sonarr/core/deprecated.dart new file mode 100644 index 00000000..2e6550f5 --- /dev/null +++ b/lib/modules/sonarr/core/deprecated.dart @@ -0,0 +1,3 @@ +export 'deprecated/qualityprofile.dart'; +export 'deprecated/rootfolder.dart'; +export 'deprecated/seriestype.dart'; diff --git a/lib/modules/sonarr/core/api/data/qualityprofile.dart b/lib/modules/sonarr/core/deprecated/qualityprofile.dart similarity index 52% rename from lib/modules/sonarr/core/api/data/qualityprofile.dart rename to lib/modules/sonarr/core/deprecated/qualityprofile.dart index aa62686a..88106029 100644 --- a/lib/modules/sonarr/core/api/data/qualityprofile.dart +++ b/lib/modules/sonarr/core/deprecated/qualityprofile.dart @@ -3,19 +3,19 @@ import 'package:hive/hive.dart'; part 'qualityprofile.g.dart'; -@HiveType(typeId: 2, adapterName: 'SonarrQualityProfileAdapter') -class SonarrQualityProfile extends HiveObject { +@HiveType(typeId: 2, adapterName: 'DeprecatedSonarrQualityProfileAdapter') +class DeprecatedSonarrQualityProfile extends HiveObject { @HiveField(0) int id; @HiveField(1) String name; - factory SonarrQualityProfile.empty() => SonarrQualityProfile( + factory DeprecatedSonarrQualityProfile.empty() => DeprecatedSonarrQualityProfile( id: -1, name: '', ); - SonarrQualityProfile({ + DeprecatedSonarrQualityProfile({ @required this.id, @required this.name, }); diff --git a/lib/modules/sonarr/core/api/data/qualityprofile.g.dart b/lib/modules/sonarr/core/deprecated/qualityprofile.g.dart similarity index 73% rename from lib/modules/sonarr/core/api/data/qualityprofile.g.dart rename to lib/modules/sonarr/core/deprecated/qualityprofile.g.dart index 673deffc..7cb76a17 100644 --- a/lib/modules/sonarr/core/api/data/qualityprofile.g.dart +++ b/lib/modules/sonarr/core/deprecated/qualityprofile.g.dart @@ -6,24 +6,24 @@ part of 'qualityprofile.dart'; // TypeAdapterGenerator // ************************************************************************** -class SonarrQualityProfileAdapter extends TypeAdapter { +class DeprecatedSonarrQualityProfileAdapter extends TypeAdapter { @override final int typeId = 2; @override - SonarrQualityProfile read(BinaryReader reader) { + DeprecatedSonarrQualityProfile read(BinaryReader reader) { final numOfFields = reader.readByte(); final fields = { for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), }; - return SonarrQualityProfile( + return DeprecatedSonarrQualityProfile( id: fields[0] as int, name: fields[1] as String, ); } @override - void write(BinaryWriter writer, SonarrQualityProfile obj) { + void write(BinaryWriter writer, DeprecatedSonarrQualityProfile obj) { writer ..writeByte(2) ..writeByte(0) @@ -38,7 +38,7 @@ class SonarrQualityProfileAdapter extends TypeAdapter { @override bool operator ==(Object other) => identical(this, other) || - other is SonarrQualityProfileAdapter && + other is DeprecatedSonarrQualityProfileAdapter && runtimeType == other.runtimeType && typeId == other.typeId; } diff --git a/lib/modules/sonarr/core/api/data/rootfolder.dart b/lib/modules/sonarr/core/deprecated/rootfolder.dart similarity index 60% rename from lib/modules/sonarr/core/api/data/rootfolder.dart rename to lib/modules/sonarr/core/deprecated/rootfolder.dart index 325d6119..19dcb2a6 100644 --- a/lib/modules/sonarr/core/api/data/rootfolder.dart +++ b/lib/modules/sonarr/core/deprecated/rootfolder.dart @@ -3,8 +3,8 @@ import 'package:hive/hive.dart'; part 'rootfolder.g.dart'; -@HiveType(typeId: 3, adapterName: 'SonarrRootFolderAdapter') -class SonarrRootFolder extends HiveObject { +@HiveType(typeId: 3, adapterName: 'DeprecatedSonarrRootFolderAdapter') +class DeprecatedSonarrRootFolder extends HiveObject { @HiveField(0) int id; @HiveField(1) @@ -12,13 +12,13 @@ class SonarrRootFolder extends HiveObject { @HiveField(2) int freeSpace; - factory SonarrRootFolder.empty() => SonarrRootFolder( + factory DeprecatedSonarrRootFolder.empty() => DeprecatedSonarrRootFolder( id: -1, path: '', freeSpace: 0, ); - SonarrRootFolder({ + DeprecatedSonarrRootFolder({ @required this.id, @required this.path, @required this.freeSpace, diff --git a/lib/modules/sonarr/core/api/data/rootfolder.g.dart b/lib/modules/sonarr/core/deprecated/rootfolder.g.dart similarity index 76% rename from lib/modules/sonarr/core/api/data/rootfolder.g.dart rename to lib/modules/sonarr/core/deprecated/rootfolder.g.dart index 55727728..2128244c 100644 --- a/lib/modules/sonarr/core/api/data/rootfolder.g.dart +++ b/lib/modules/sonarr/core/deprecated/rootfolder.g.dart @@ -6,17 +6,17 @@ part of 'rootfolder.dart'; // TypeAdapterGenerator // ************************************************************************** -class SonarrRootFolderAdapter extends TypeAdapter { +class DeprecatedSonarrRootFolderAdapter extends TypeAdapter { @override final int typeId = 3; @override - SonarrRootFolder read(BinaryReader reader) { + DeprecatedSonarrRootFolder read(BinaryReader reader) { final numOfFields = reader.readByte(); final fields = { for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), }; - return SonarrRootFolder( + return DeprecatedSonarrRootFolder( id: fields[0] as int, path: fields[1] as String, freeSpace: fields[2] as int, @@ -24,7 +24,7 @@ class SonarrRootFolderAdapter extends TypeAdapter { } @override - void write(BinaryWriter writer, SonarrRootFolder obj) { + void write(BinaryWriter writer, DeprecatedSonarrRootFolder obj) { writer ..writeByte(3) ..writeByte(0) @@ -41,7 +41,7 @@ class SonarrRootFolderAdapter extends TypeAdapter { @override bool operator ==(Object other) => identical(this, other) || - other is SonarrRootFolderAdapter && + other is DeprecatedSonarrRootFolderAdapter && runtimeType == other.runtimeType && typeId == other.typeId; } diff --git a/lib/modules/sonarr/core/deprecated/seriestype.dart b/lib/modules/sonarr/core/deprecated/seriestype.dart new file mode 100644 index 00000000..3a3b68df --- /dev/null +++ b/lib/modules/sonarr/core/deprecated/seriestype.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; + +part 'seriestype.g.dart'; + +@HiveType(typeId: 4, adapterName: 'DeprecatedSonarrSeriesTypeAdapter') +class DeprecatedSonarrSeriesType extends HiveObject { + @HiveField(0) + String type; + + factory DeprecatedSonarrSeriesType.empty() => DeprecatedSonarrSeriesType( + type: '', + ); + + DeprecatedSonarrSeriesType({ + @required this.type, + }); +} diff --git a/lib/modules/sonarr/core/api/data/seriestype.g.dart b/lib/modules/sonarr/core/deprecated/seriestype.g.dart similarity index 72% rename from lib/modules/sonarr/core/api/data/seriestype.g.dart rename to lib/modules/sonarr/core/deprecated/seriestype.g.dart index a372ef86..bd6fe506 100644 --- a/lib/modules/sonarr/core/api/data/seriestype.g.dart +++ b/lib/modules/sonarr/core/deprecated/seriestype.g.dart @@ -6,23 +6,23 @@ part of 'seriestype.dart'; // TypeAdapterGenerator // ************************************************************************** -class SonarrSeriesTypeAdapter extends TypeAdapter { +class DeprecatedSonarrSeriesTypeAdapter extends TypeAdapter { @override final int typeId = 4; @override - SonarrSeriesType read(BinaryReader reader) { + DeprecatedSonarrSeriesType read(BinaryReader reader) { final numOfFields = reader.readByte(); final fields = { for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), }; - return SonarrSeriesType( + return DeprecatedSonarrSeriesType( type: fields[0] as String, ); } @override - void write(BinaryWriter writer, SonarrSeriesType obj) { + void write(BinaryWriter writer, DeprecatedSonarrSeriesType obj) { writer ..writeByte(1) ..writeByte(0) @@ -35,7 +35,7 @@ class SonarrSeriesTypeAdapter extends TypeAdapter { @override bool operator ==(Object other) => identical(this, other) || - other is SonarrSeriesTypeAdapter && + other is DeprecatedSonarrSeriesTypeAdapter && runtimeType == other.runtimeType && typeId == other.typeId; } diff --git a/lib/modules/sonarr/core/dialogs.dart b/lib/modules/sonarr/core/dialogs.dart index aaee39e6..c0245c02 100644 --- a/lib/modules/sonarr/core/dialogs.dart +++ b/lib/modules/sonarr/core/dialogs.dart @@ -1,80 +1,164 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; -import 'package:intl/intl.dart'; import 'package:lunasea/modules/sonarr.dart'; class SonarrDialogs { SonarrDialogs._(); - - static Future> downloadWarning(BuildContext context) async { - bool _flag = false; - void _setValues(bool flag) { - _flag = flag; - Navigator.of(context).pop(); - } + static Future> globalSettings(BuildContext context) async { + bool _flag = false; + SonarrGlobalSettingsType _value; - await LSDialog.dialog( - context: context, - title: 'Download Release', - buttons: [ - LSDialog.button( - text: 'Download', - onPressed: () => _setValues(true), - ), - ], - content: [ - LSDialog.textContent(text: 'Are you sure you want to download this release? It has been marked as a rejected release by Sonarr.') - ], - contentPadding: LSDialog.textDialogContentPadding(), - ); - return [_flag]; - } - - static Future> deleteSeries(BuildContext context) async { - bool _flag = false; - bool _files = false; - - void _setValues(bool flag, bool files) { + void _setValues(bool flag, SonarrGlobalSettingsType value) { _flag = flag; - _files = files; - Navigator.of(context).pop(); + _value = value; + Navigator.of(context, rootNavigator: true).pop(); } await LSDialog.dialog( context: context, - title: 'Remove Series', - buttons: [ - LSDialog.button( - text: 'Remove + Files', - textColor: LSColors.red, - onPressed: () => _setValues(true, true), + title: 'Sonarr Settings', + content: List.generate( + SonarrGlobalSettingsType.values.length, + (index) => LSDialog.tile( + text: SonarrGlobalSettingsType.values[index].name, + icon: SonarrGlobalSettingsType.values[index].icon, + iconColor: LunaColours.list(index), + onTap: () => _setValues(true, SonarrGlobalSettingsType.values[index]), ), - LSDialog.button( - text: 'Remove', - textColor: LSColors.red, - onPressed: () => _setValues(true, false), - ), - ], - content: [ - LSDialog.textContent(text: 'Are you sure you want to remove the series from Sonarr?'), - ], - contentPadding: LSDialog.textDialogContentPadding(), + ), + contentPadding: LSDialog.listDialogContentPadding(), ); - return [_flag, _files]; + return [_flag, _value]; } - static Future> searchAllMissing(BuildContext context) async { + static Future> seriesSettings(BuildContext context, SonarrSeries series) async { + bool _flag = false; + SonarrSeriesSettingsType _value; + + void _setValues(bool flag, SonarrSeriesSettingsType value) { + _flag = flag; + _value = value; + Navigator.of(context, rootNavigator: true).pop(); + } + + await LSDialog.dialog( + context: context, + title: series.title, + content: List.generate( + SonarrSeriesSettingsType.values.length, + (index) => LSDialog.tile( + text: SonarrSeriesSettingsType.values[index].name(series), + icon: SonarrSeriesSettingsType.values[index].icon(series), + iconColor: LunaColours.list(index), + onTap: () => _setValues(true, SonarrSeriesSettingsType.values[index]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _value]; + } + + static Future> episodeSettings(BuildContext context, SonarrEpisode episode) async { + bool _flag = false; + SonarrEpisodeSettingsType _value; + + void _setValues(bool flag, SonarrEpisodeSettingsType value) { + _flag = flag; + _value = value; + Navigator.of(context, rootNavigator: true).pop(); + } + + await LSDialog.dialog( + context: context, + title: episode.title, + content: List.generate( + episode.hasFile + ? SonarrEpisodeSettingsType.values.length + : SonarrEpisodeSettingsType.values.length-1, + (index) => LSDialog.tile( + text: SonarrEpisodeSettingsType.values[index].name(episode), + icon: SonarrEpisodeSettingsType.values[index].icon(episode), + iconColor: LunaColours.list(index), + onTap: () => _setValues(true, SonarrEpisodeSettingsType.values[index]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _value]; + } + + static Future> seasonSettings(BuildContext context, int seasonNumber) async { + bool _flag = false; + SonarrSeasonSettingsType _value; + + void _setValues(bool flag, SonarrSeasonSettingsType value) { + _flag = flag; + _value = value; + Navigator.of(context, rootNavigator: true).pop(); + } + + await LSDialog.dialog( + context: context, + title: seasonNumber == 0 ? 'Specials' : 'Season $seasonNumber', + content: List.generate( + context.read().enableVersion3 + ? SonarrSeasonSettingsType.values.length + : SonarrSeasonSettingsType.values.length-1, + (index) => LSDialog.tile( + text: SonarrSeasonSettingsType.values[index].name, + icon: SonarrSeasonSettingsType.values[index].icon, + iconColor: LunaColours.list(index), + onTap: () => _setValues(true, SonarrSeasonSettingsType.values[index]), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + return [_flag, _value]; + } + + static Future> setDefaultPage(BuildContext context, { + @required List titles, + @required List icons, + }) async { + bool _flag = false; + int _index = 0; + + void _setValues(bool flag, int index) { + _flag = flag; + _index = index; + Navigator.of(context, rootNavigator: true).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Default Page', + content: List.generate( + titles.length, + (index) => LSDialog.tile( + text: titles[index], + icon: icons[index], + iconColor: LunaColours.list(index), + onTap: () => _setValues(true, index), + ), + ), + contentPadding: LSDialog.listDialogContentPadding(), + ); + + return [_flag, _index]; + } + + static Future> searchAllMissingEpisodes(BuildContext context) async { bool _flag = false; void _setValues(bool flag) { _flag = flag; - Navigator.of(context).pop(); + Navigator.of(context, rootNavigator: true).pop(); } await LSDialog.dialog( context: context, - title: 'Search All Missing', + title: 'Missing Episodes', buttons: [ LSDialog.button( text: 'Search', @@ -89,115 +173,68 @@ class SonarrDialogs { return [_flag]; } - static Future> globalSettings(BuildContext context) async { - List> _options = [ - ['View Web GUI', Icons.language, 'web_gui'], - ['Update Library', Icons.autorenew, 'update_library'], - ['Run RSS Sync', Icons.rss_feed, 'rss_sync'], - ['Search All Missing', Icons.search, 'missing_search'], - ['Backup Database', Icons.save, 'backup'], - ]; + static Future> editLanguageProfiles(BuildContext context, List profiles) async { bool _flag = false; - String _value = ''; + SonarrLanguageProfile profile; - void _setValues(bool flag, String value) { + void _setValues(bool flag, SonarrLanguageProfile value) { _flag = flag; - _value = value; - Navigator.of(context).pop(); + profile = value; + Navigator.of(context, rootNavigator: true).pop(); } await LSDialog.dialog( context: context, - title: 'Sonarr Settings', + title: 'Language Profile', content: List.generate( - _options.length, + profiles.length, (index) => LSDialog.tile( - text: _options[index][0], - icon: _options[index][1], - iconColor: LSColors.list(index), - onTap: () => _setValues(true, _options[index][2]), + text: profiles[index].name, + icon: Icons.portrait, + iconColor: LunaColours.list(index), + onTap: () => _setValues(true, profiles[index]), ), ), contentPadding: LSDialog.listDialogContentPadding(), ); - return [_flag, _value]; + return [_flag, profile]; } - static Future> editEpisode(BuildContext context, String title, bool monitored, bool canDelete) async { - List> _options = [ - monitored - ? ['Unmonitor Episode', Icons.turned_in_not, 'monitor_status'] - : ['Monitor Episode', Icons.turned_in, 'monitor_status'], - ['Automatic Search', Icons.search, 'search_automatic'], - ['Interactive Search', Icons.youtube_searched_for, 'search_manual'], - if(canDelete) ['Delete File', Icons.delete, 'delete_file'], - ]; + static Future> editQualityProfile(BuildContext context, List profiles) async { bool _flag = false; - String _value = ''; + SonarrQualityProfile profile; - void _setValues(bool flag, String value) { + void _setValues(bool flag, SonarrQualityProfile value) { _flag = flag; - _value = value; - Navigator.of(context).pop(); + profile = value; + Navigator.of(context, rootNavigator: true).pop(); } await LSDialog.dialog( context: context, - title: title, + title: 'Quality Profile', content: List.generate( - _options.length, + profiles.length, (index) => LSDialog.tile( - text: _options[index][0], - icon: _options[index][1], - iconColor: LSColors.list(index), - onTap: () => _setValues(true, _options[index][2]), + text: profiles[index].name, + icon: Icons.portrait, + iconColor: LunaColours.list(index), + onTap: () => _setValues(true, profiles[index]), ), ), contentPadding: LSDialog.listDialogContentPadding(), ); - return [_flag, _value]; - } - - static Future> editSeries(BuildContext context, SonarrCatalogueData entry) async { - List> _options = [ - ['Edit Series', Icons.edit, 'edit_series'], - ['Refresh Series', Icons.refresh, 'refresh_series'], - ['Remove Series', Icons.delete, 'remove_series'], - ]; - bool _flag = false; - String _value = ''; - - void _setValues(bool flag, String value) { - _flag = flag; - _value = value; - Navigator.of(context).pop(); - } - - await LSDialog.dialog( - context: context, - title: entry.title, - content: List.generate( - _options.length, - (index) => LSDialog.tile( - icon: _options[index][1], - iconColor: LSColors.list(index), - text: _options[index][0], - onTap: () => _setValues(true, _options[index][2]), - ), - ), - contentPadding: LSDialog.listDialogContentPadding(), - ); - return [_flag, _value]; + return [_flag, profile]; } static Future> editRootFolder(BuildContext context, List folders) async { bool _flag = false; SonarrRootFolder _folder; - void _setValues(bool flag, SonarrRootFolder folder) { + void _setValues(bool flag, SonarrRootFolder value) { _flag = flag; - _folder = folder; - Navigator.of(context).pop(); + _folder = value; + Navigator.of(context, rootNavigator: true).pop(); } await LSDialog.dialog( @@ -209,11 +246,11 @@ class SonarrDialogs { text: folders[index].path, subtitle: LSDialog.richText( children: [ - LSDialog.bolded(text: folders[index].freeSpace.lsBytes_BytesToString()), + LSDialog.bolded(text: folders[index].freeSpace.lsBytes_BytesToString()) ], ), icon: Icons.folder, - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, folders[index]), ), ), @@ -222,25 +259,25 @@ class SonarrDialogs { return [_flag, _folder]; } - static Future> editMonitoringStatus(BuildContext context) async { + static Future> editMonitorStatus(BuildContext context) async { bool _flag = false; SonarrMonitorStatus _status; void _setValues(bool flag, SonarrMonitorStatus status) { _flag = flag; _status = status; - Navigator.of(context).pop(); + Navigator.of(context, rootNavigator: true).pop(); } await LSDialog.dialog( context: context, - title: 'Monitoring Status', + title: 'Monitor Status', content: List.generate( SonarrMonitorStatus.values.length, (index) => LSDialog.tile( text: SonarrMonitorStatus.values[index].name, icon: Icons.view_list, - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, SonarrMonitorStatus.values[index]), ), ), @@ -249,33 +286,6 @@ class SonarrDialogs { return [_flag, _status]; } - static Future> editQualityProfile(BuildContext context, List qualities) async { - bool _flag = false; - SonarrQualityProfile _quality; - - void _setValues(bool flag, SonarrQualityProfile quality) { - _flag = flag; - _quality = quality; - Navigator.of(context).pop(); - } - - await LSDialog.dialog( - context: context, - title: 'Quality Profile', - content: List.generate( - qualities.length, - (index) => LSDialog.tile( - text: qualities[index].name, - icon: Icons.portrait, - iconColor: LSColors.list(index), - onTap: () => _setValues(true, qualities[index]), - ), - ), - contentPadding: LSDialog.listDialogContentPadding(), - ); - return [_flag, _quality]; - } - static Future> editSeriesType(BuildContext context) async { bool _flag = false; SonarrSeriesType _type; @@ -283,19 +293,19 @@ class SonarrDialogs { void _setValues(bool flag, SonarrSeriesType type) { _flag = flag; _type = type; - Navigator.of(context).pop(); + Navigator.of(context, rootNavigator: true).pop(); } await LSDialog.dialog( context: context, title: 'Series Type', content: List.generate( - SonarrConstants.SERIES_TYPES.length, + SonarrSeriesType.values.length, (index) => LSDialog.tile( - text: toBeginningOfSentenceCase(SonarrConstants.SERIES_TYPES[index].type), - icon: Icons.tab, - iconColor: LSColors.list(index), - onTap: () => _setValues(true, SonarrConstants.SERIES_TYPES[index]) + text: SonarrSeriesType.values[index].value.lsLanguage_Capitalize(), + icon: Icons.folder_open, + iconColor: LunaColours.list(index), + onTap: () => _setValues(true, SonarrSeriesType.values[index]), ), ), contentPadding: LSDialog.listDialogContentPadding(), @@ -303,17 +313,85 @@ class SonarrDialogs { return [_flag, _type]; } - static Future> searchEntireSeason(BuildContext context, int seasonNumber) async { + static Future> confirmDeleteSeries(BuildContext context) async { bool _flag = false; void _setValues(bool flag) { _flag = flag; - Navigator.of(context).pop(); + Navigator.of(context, rootNavigator: true).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Remove Series', + buttons: [ + LSDialog.button( + text: 'Remove', + textColor: LunaColours.red, + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.textContent(text: 'Are you sure you want to remove the series from Sonarr?\n'), + Selector( + selector: (_, state) => state.removeSeriesDeleteFiles, + builder: (context, value, text) => CheckboxListTile( + title: text, + value: value, + onChanged: (selected) => Provider.of(context, listen: false).removeSeriesDeleteFiles = selected, + contentPadding: LSDialog.tileContentPadding(), + ), + child: Text( + 'Delete Files', + style: TextStyle( + fontSize: LSDialog.BODY_SIZE, + color: Colors.white, + ), + ), + ), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag]; + } + + static Future> confirmDeleteEpisodeFile(BuildContext context) async { + bool _flag = false; + + void _setValues(bool flag) { + _flag = flag; + Navigator.of(context, rootNavigator: true).pop(); + } + + await LSDialog.dialog( + context: context, + title: 'Delete Episode File', + buttons: [ + LSDialog.button( + text: 'Delete', + textColor: LunaColours.red, + onPressed: () => _setValues(true), + ), + ], + content: [ + LSDialog.textContent(text: 'Are you sure you want to delete this episode file?'), + ], + contentPadding: LSDialog.textDialogContentPadding(), + ); + return [_flag]; + } + + static Future> confirmSeasonSearch(BuildContext context, int seasonNumber) async { + bool _flag = false; + + void _setValues(bool flag) { + _flag = flag; + Navigator.of(context, rootNavigator: true).pop(); } await LSDialog.dialog( context: context, - title: 'Episode Search', + title: 'Season Search', buttons: [ LSDialog.button( text: 'Search', @@ -331,58 +409,4 @@ class SonarrDialogs { ); return [_flag]; } - - static Future> deleteEpisodeFile(BuildContext context) async { - bool _flag = false; - - void _setValues(bool flag) { - _flag = flag; - Navigator.of(context).pop(); - } - - await LSDialog.dialog( - context: context, - title: 'Delete Episode File', - buttons: [ - LSDialog.button( - text: 'Delete', - textColor: LSColors.red, - onPressed: () => _setValues(true), - ), - ], - content: [ - LSDialog.textContent(text: 'Are you sure you want to delete this episode file?'), - ], - contentPadding: LSDialog.textDialogContentPadding(), - ); - return [_flag]; - } - - static Future> defaultPage(BuildContext context) async { - bool _flag = false; - int _index = 0; - - void _setValues(bool flag, int index) { - _flag = flag; - _index = index; - Navigator.of(context, rootNavigator: true).pop(); - } - - await LSDialog.dialog( - context: context, - title: 'Default Page', - content: List.generate( - SonarrNavigationBar.titles.length, - (index) => LSDialog.tile( - text: SonarrNavigationBar.titles[index], - icon: SonarrNavigationBar.icons[index], - iconColor: LSColors.list(index), - onTap: () => _setValues(true, index), - ), - ), - contentPadding: LSDialog.listDialogContentPadding(), - ); - - return [_flag, _index]; - } } diff --git a/lib/modules/sonarr/core/extensions.dart b/lib/modules/sonarr/core/extensions.dart new file mode 100644 index 00000000..d1aa20a3 --- /dev/null +++ b/lib/modules/sonarr/core/extensions.dart @@ -0,0 +1,6 @@ +export 'extensions/sonarr_calendar.dart'; +export 'extensions/sonarr_episode.dart'; +export 'extensions/sonarr_history_event_type.dart'; +export 'extensions/sonarr_series_lookup.dart'; +export 'extensions/sonarr_series_season.dart'; +export 'extensions/sonarr_series.dart'; diff --git a/lib/modules/sonarr/core/extensions/sonarr_calendar.dart b/lib/modules/sonarr/core/extensions/sonarr_calendar.dart new file mode 100644 index 00000000..89c645cd --- /dev/null +++ b/lib/modules/sonarr/core/extensions/sonarr_calendar.dart @@ -0,0 +1,12 @@ +import 'package:intl/intl.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +extension SonarrCalendarExtension on SonarrCalendar { + String get lunaAirTime { + if(this.airDateUtc != null) return LunaSeaDatabaseValue.USE_24_HOUR_TIME.data + ? DateFormat.Hm().format(this.airDateUtc.toLocal()) + : DateFormat('hh:mm a').format(this.airDateUtc.toLocal()); + return Constants.TEXT_EMDASH; + } +} \ No newline at end of file diff --git a/lib/modules/sonarr/core/extensions/sonarr_episode.dart b/lib/modules/sonarr/core/extensions/sonarr_episode.dart new file mode 100644 index 00000000..25491cee --- /dev/null +++ b/lib/modules/sonarr/core/extensions/sonarr_episode.dart @@ -0,0 +1,6 @@ +import 'package:lunasea/modules/sonarr.dart'; + +extension SonarrEpisodeExtension on SonarrEpisode { + /// Creates a clone of the [SonarrEpisode] object (deep copy). + SonarrEpisode clone() => SonarrEpisode.fromJson(this.toJson()); +} diff --git a/lib/modules/sonarr/core/extensions/sonarr_history_event_type.dart b/lib/modules/sonarr/core/extensions/sonarr_history_event_type.dart new file mode 100644 index 00000000..12c697e5 --- /dev/null +++ b/lib/modules/sonarr/core/extensions/sonarr_history_event_type.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +extension SonarrHistoryEventTypeLunaExtension on SonarrHistoryEventType { + String lunaMessage(SonarrHistoryRecord data) { + switch(this) { + case SonarrHistoryEventType.EPISODE_FILE_RENAMED: return 'Episode File Renamed'; + case SonarrHistoryEventType.EPISODE_FILE_DELETED: return 'Episode File Deleted (${data.data.reason})'; + case SonarrHistoryEventType.DOWNLOAD_FOLDER_IMPORTED: return 'Imported Episode File (${data?.quality?.quality?.name ?? 'Unknown'})'; + case SonarrHistoryEventType.DOWNLOAD_FAILED: return 'Download Failed'; + case SonarrHistoryEventType.GRABBED: return 'Grabbed From ${data.data.indexer}'; + } + return 'Unknown Event'; + } + + Color get lunaColour { + switch(this) { + case SonarrHistoryEventType.EPISODE_FILE_RENAMED: return LunaColours.blue; + case SonarrHistoryEventType.EPISODE_FILE_DELETED: return LunaColours.red; + case SonarrHistoryEventType.DOWNLOAD_FOLDER_IMPORTED: return LunaColours.accent; + case SonarrHistoryEventType.DOWNLOAD_FAILED: return LunaColours.red; + case SonarrHistoryEventType.GRABBED: return LunaColours.orange; + } + return LunaColours.blueGrey; + } +} diff --git a/lib/modules/sonarr/core/extensions/sonarr_series.dart b/lib/modules/sonarr/core/extensions/sonarr_series.dart new file mode 100644 index 00000000..b00b0760 --- /dev/null +++ b/lib/modules/sonarr/core/extensions/sonarr_series.dart @@ -0,0 +1,74 @@ +import 'package:intl/intl.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +extension SonarrSeriesExtension on SonarrSeries { + int get lunaPercentageComplete { + int _total = this.episodeCount ?? 0; + int _available = this.episodeFileCount ?? 0; + return _total == 0 ? 0 : ((_available/_total)*100).round(); + } + + String get lunaRuntime { + if(this.runtime == null) return 'Unknown'; + return this.runtime == 1 ? '1 Minute' : '${this.runtime} Minutes'; + } + + String get lunaNextAiring { + if(this.nextAiring == null) return Constants.TEXT_EMDASH; + return DateFormat('MMMM dd, y').format(this.nextAiring.toLocal()); + } + + String get lunaDateAdded { + if(this.added == null) return 'Unknown'; + return DateFormat('MMMM dd, y').format(this.added.toLocal()); + } + + String get lunaAirTime { + if(this.previousAiring != null) return LunaSeaDatabaseValue.USE_24_HOUR_TIME.data + ? DateFormat.Hm().format(this.previousAiring.toLocal()) + : DateFormat('hh:mm a').format(this.previousAiring.toLocal()); + if(this.airTime == null) return 'Unknown'; + return this.airTime; + } + + String get lunaSeriesType { + if(this.seriesType == null) return 'Unknown'; + return this.seriesType.value.lsLanguage_Capitalize(); + } + + String get lunaSeasonCount { + if(this.seasonCount == null) return 'Unknown'; + return this.seasonCount == 1 + ? '1 Season' + : '${this.seasonCount} Seasons'; + } + + String get lunaSizeOnDisk { + if(this.sizeOnDisk == null) return '0.0 B'; + return this.sizeOnDisk.lsBytes_BytesToString(decimals: 1); + } + + String get lunaAirsOn { + if(this.status == 'ended') return 'Aired on ${this.network ?? Constants.TEXT_EMDASH}'; + return '${this.lunaAirTime ?? 'Unknown Time'} on ${this.network ?? Constants.TEXT_EMDASH}'; + } + + String get lunaEpisodeCount { + return '${this.episodeFileCount ?? 0}/${this.episodeCount ?? 0} (${this.lunaPercentageComplete}%)'; + } + + /// Creates a clone of the [SonarrSeries] object (deep copy). + SonarrSeries clone() => SonarrSeries.fromJson(this.toJson()); + + /// Copies changes from a [SonarrSeriesEditState] state object back to the [SonarrSeries] object. + void updateEdits(SonarrSeriesEditState edits) { + this.monitored = edits?.monitored ?? this.monitored; + this.seasonFolder = edits?.useSeasonFolders ?? this.seasonFolder; + this.path = edits?.seriesPath ?? this.path; + this.profileId = edits?.qualityProfile?.id ?? this.profileId; + this.qualityProfileId = edits?.qualityProfile?.id ?? this.qualityProfileId; + this.languageProfileId = edits?.languageProfile?.id ?? this.languageProfileId; + this.seriesType = edits?.seriesType ?? this.seriesType; + } +} diff --git a/lib/modules/sonarr/core/extensions/sonarr_series_lookup.dart b/lib/modules/sonarr/core/extensions/sonarr_series_lookup.dart new file mode 100644 index 00000000..8f4cf4fc --- /dev/null +++ b/lib/modules/sonarr/core/extensions/sonarr_series_lookup.dart @@ -0,0 +1,8 @@ +import 'package:lunasea/modules/sonarr.dart'; + +extension SonarrSeriesLookupExtension on SonarrSeriesLookup { + String get lunaBannerURL => this.images.firstWhere( + (element) => element.coverType == 'banner', + orElse: () => null, + )?.url; +} \ No newline at end of file diff --git a/lib/modules/sonarr/core/extensions/sonarr_series_season.dart b/lib/modules/sonarr/core/extensions/sonarr_series_season.dart new file mode 100644 index 00000000..785d90cd --- /dev/null +++ b/lib/modules/sonarr/core/extensions/sonarr_series_season.dart @@ -0,0 +1,9 @@ +import 'package:lunasea/modules/sonarr.dart'; + +extension SonarrSeriesSeasonExtension on SonarrSeriesSeason { + int get lunaPercentageComplete { + int _total = this?.statistics?.totalEpisodeCount ?? 0; + int _available = this?.statistics?.episodeFileCount ?? 0; + return _total == 0 ? 0 : ((_available/_total)*100).round(); + } +} diff --git a/lib/modules/sonarr/core/router.dart b/lib/modules/sonarr/core/router.dart new file mode 100644 index 00000000..ea31ed3e --- /dev/null +++ b/lib/modules/sonarr/core/router.dart @@ -0,0 +1,20 @@ +import 'package:fluro_fork/fluro_fork.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrRouter { + SonarrRouter._(); + + static void initialize(Router router) { + SonarrHomeRouter.defineRoutes(router); + // Series + SonarrSeriesAddRouter.defineRoutes(router); + SonarrSeriesAddDetailsRouter.defineRoutes(router); + SonarrSeriesEditRouter.defineRoutes(router); + SonarrSeriesDetailsRouter.defineRoutes(router); + SonarrSeriesSeasonDetailsRouter.defineRoutes(router); + // Other + SonarrQueueRouter.defineRoutes(router); + SonarrReleasesRouter.defineRoutes(router); + SonarrTagsRouter.defineRoutes(router); + } +} diff --git a/lib/modules/sonarr/core/sorting.dart b/lib/modules/sonarr/core/sorting.dart deleted file mode 100644 index e058f21e..00000000 --- a/lib/modules/sonarr/core/sorting.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'sorting/catalogue.dart'; -export 'sorting/releases.dart'; diff --git a/lib/modules/sonarr/core/sorting/catalogue.dart b/lib/modules/sonarr/core/sorting/catalogue.dart deleted file mode 100644 index 9f1f040e..00000000 --- a/lib/modules/sonarr/core/sorting/catalogue.dart +++ /dev/null @@ -1,157 +0,0 @@ -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -enum SonarrCatalogueSorting { - alphabetical, - dateAdded, - episodes, - network, - nextAiring, - quality, - size, - type, -} - -extension SonarrCatalogueSortingExtension on SonarrCatalogueSorting { - static _Sorter _sorter = _Sorter(); - - String get value { - switch(this) { - case SonarrCatalogueSorting.alphabetical: return 'abc'; - case SonarrCatalogueSorting.episodes: return 'episodes'; - case SonarrCatalogueSorting.dateAdded: return 'date_added'; - case SonarrCatalogueSorting.size: return 'size'; - case SonarrCatalogueSorting.type: return 'type'; - case SonarrCatalogueSorting.network: return 'network'; - case SonarrCatalogueSorting.quality: return 'quality'; - case SonarrCatalogueSorting.nextAiring: return 'next_airing'; - } - throw Exception('value not found'); - } - - String get readable { - switch(this) { - case SonarrCatalogueSorting.alphabetical: return 'Alphabetical'; - case SonarrCatalogueSorting.dateAdded: return 'Date Added'; - case SonarrCatalogueSorting.episodes: return 'Episodes'; - case SonarrCatalogueSorting.network: return 'Network'; - case SonarrCatalogueSorting.size: return 'Size'; - case SonarrCatalogueSorting.type: return 'Type'; - case SonarrCatalogueSorting.quality: return 'Quality Profile'; - case SonarrCatalogueSorting.nextAiring: return 'Next Airing'; - } - throw Exception('readable not found'); - } - - List sort( - List data, - bool ascending - ) => _sorter.byType(data, this, ascending); -} - -class _Sorter extends Sorter { - @override - List byType( - List data, - SonarrCatalogueSorting type, - bool ascending, - ) { - switch(type) { - case SonarrCatalogueSorting.alphabetical: return _alphabetical(data, ascending); - case SonarrCatalogueSorting.dateAdded: return _dateAdded(data, ascending); - case SonarrCatalogueSorting.size: return _size(data, ascending); - case SonarrCatalogueSorting.type: return _type(data, ascending); - case SonarrCatalogueSorting.network: return _network(data, ascending); - case SonarrCatalogueSorting.quality: return _quality(data, ascending); - case SonarrCatalogueSorting.episodes: return _episodes(data, ascending); - case SonarrCatalogueSorting.nextAiring: return _nextAiring(data, ascending); - } - throw Exception('sorting type not found'); - } - - List _alphabetical(List data, bool ascending) { - List _data = new List.from(data, growable: false); - ascending - ? _data.sort((a,b) => a.sortTitle.compareTo(b.sortTitle)) - : _data.sort((a,b) => b.sortTitle.compareTo(a.sortTitle)); - return _data; - } - - List _dateAdded(List data, bool ascending) { - List _data = _alphabetical(data, true); - List _hasNoDate = _data.where((item) => item.dateAddedObject == null).toList(); - List _hasDate = _data.where((item) => item.dateAddedObject != null).toList(); - _hasDate.sort((a,b) { - return ascending - ? a.dateAddedObject.compareTo(b.dateAddedObject) - : b.dateAddedObject.compareTo(a.dateAddedObject); - }); - return [..._hasDate, ..._hasNoDate]; - } - - List _size(List data, bool ascending) { - List _data = new List.from(data, growable: false); - ascending - ? _data.sort((a,b) => a.sizeOnDisk.compareTo(b.sizeOnDisk)) - : _data.sort((a,b) => b.sizeOnDisk.compareTo(a.sizeOnDisk)); - return _data; - } - - List _type(List data, bool ascending) { - List _data = _alphabetical(data, true); - List _daily = _data.where((value) => value.type == 'daily').toList(); - List _anime = _data.where((value) => value.type == 'anime').toList(); - List _standard = _data.where((value) => value.type == 'standard').toList(); - return ascending - ? [..._anime, ..._daily, ..._standard] - : [..._standard, ..._daily, ..._anime]; - } - - List _network(List data, bool ascending) { - List _data = _alphabetical(data, true); - ascending - ? _data.sort((a,b) => a.network.toLowerCase().compareTo(b.network.toLowerCase())) - : _data.sort((a,b) => b.network.toLowerCase().compareTo(a.network.toLowerCase())); - return _data; - } - - List _quality(List data, bool ascending) { - List _data = _alphabetical(data, true); - ascending - ? _data.sort((a,b) => a.qualityProfile.compareTo(b.qualityProfile)) - : _data.sort((a,b) => b.qualityProfile.compareTo(a.qualityProfile)); - return _data; - } - - List _episodes(List data, bool ascending) { - List _data = _alphabetical(data, true); - _data.sort((a,b) { - int episodeCountA = a.episodeCount ?? 0; - int availableEpisodeCountA = a.episodeFileCount ?? 0; - int episodeCountB = b.episodeCount ?? 0; - int availableEpisodeCountB = b.episodeFileCount ?? 0; - int percentageA = episodeCountA == 0 - ? 0 - : ((availableEpisodeCountA/episodeCountA)*100).round(); - int percentageB = episodeCountA == 0 - ? 0 - : ((availableEpisodeCountB/episodeCountB)*100).round(); - return ascending - ? percentageA.compareTo(percentageB) - : percentageB.compareTo(percentageA); - }); - return _data; - } - - List _nextAiring(List data, bool ascending) { - List _data = _alphabetical(data, true); - List _hasNoDate = _data.where((item) => item.nextAiringObject == null).toList(); - List _hasDate = _data.where((item) => item.nextAiringObject != null).toList(); - _hasDate.sort((a,b) { - return ascending - ? a.nextAiringObject.compareTo(b.nextAiringObject) - : b.nextAiringObject.compareTo(a.nextAiringObject); - }); - return [..._hasDate, ..._hasNoDate]; - } -} diff --git a/lib/modules/sonarr/core/sorting/releases.dart b/lib/modules/sonarr/core/sorting/releases.dart deleted file mode 100644 index 1480fe5f..00000000 --- a/lib/modules/sonarr/core/sorting/releases.dart +++ /dev/null @@ -1,114 +0,0 @@ -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -enum SonarrReleasesSorting { - age, - alphabetical, - seeders, - size, - type, - weight, -} - -extension SonarrReleasesSortingExtension on SonarrReleasesSorting { - static _Sorter _sorter = _Sorter(); - - String get value { - switch(this) { - case SonarrReleasesSorting.age: return 'age'; - case SonarrReleasesSorting.alphabetical: return 'abc'; - case SonarrReleasesSorting.seeders: return 'seeders'; - case SonarrReleasesSorting.weight: return 'weight'; - case SonarrReleasesSorting.type: return 'type'; - case SonarrReleasesSorting.size: return 'size'; - } - throw Exception('value not found'); - } - - String get readable { - switch(this) { - case SonarrReleasesSorting.age: return 'Age'; - case SonarrReleasesSorting.alphabetical: return 'Alphabetical'; - case SonarrReleasesSorting.seeders: return 'Seeders'; - case SonarrReleasesSorting.weight: return 'Weight'; - case SonarrReleasesSorting.type: return 'Type'; - case SonarrReleasesSorting.size: return 'Size'; - } - throw Exception('readable not found'); - } - - List sort( - List data, - bool ascending - ) => _sorter.byType(data, this, ascending); -} - -class _Sorter extends Sorter { - @override - List byType( - List data, - SonarrReleasesSorting type, - bool ascending, - ) { - switch(type) { - case SonarrReleasesSorting.age: return _age(data, ascending); - case SonarrReleasesSorting.alphabetical: return _alphabetical(data, ascending); - case SonarrReleasesSorting.seeders: return _seeders(data, ascending); - case SonarrReleasesSorting.weight: return _weight(data, ascending); - case SonarrReleasesSorting.type: return _type(data, ascending); - case SonarrReleasesSorting.size: return _size(data, ascending); - } - throw Exception('sorting type not found'); - } - - List _alphabetical(List data, bool ascending) { - List _data = new List.from(data, growable: false); - ascending - ? _data.sort((a,b) => a.title.compareTo(b.title)) - : _data.sort((a,b) => b.title.compareTo(a.title)); - return _data; - } - - List _weight(List data, bool ascending) { - List _data = new List.from(data, growable: false); - ascending - ? _data.sort((a,b) => a.releaseWeight.compareTo(b.releaseWeight)) - : _data.sort((a,b) => b.releaseWeight.compareTo(a.releaseWeight)); - return _data; - } - - List _type(List data, bool ascending) { - List _data = new List.from(data, growable: false); - List _usenet = _data.where((value) => !value.isTorrent).toList(); - List _torrent = _data.where((value) => value.isTorrent).toList(); - return ascending - ? [..._usenet, ..._torrent] - : [..._torrent, ..._usenet]; - } - - List _age(List data, bool ascending) { - List _data = new List.from(data, growable: false); - ascending - ? _data.sort((a,b) => a.ageHours.compareTo(b.ageHours)) - : _data.sort((a,b) => b.ageHours.compareTo(a.ageHours)); - return _data; - } - - List _seeders(List data, bool ascending) { - List _data = new List.from(data, growable: false); - List _usenet = _data.where((value) => !value.isTorrent).toList(); - List _torrent = _data.where((value) => value.isTorrent).toList(); - ascending - ? _torrent.sort((a,b) => b.seeders.compareTo(a.seeders)) - : _torrent.sort((a,b) => a.seeders.compareTo(b.seeders)); - return [..._torrent, ..._usenet]; - } - - List _size(List data, bool ascending) { - List _data = new List.from(data, growable: false); - ascending - ? _data.sort((a,b) => a.size.compareTo(b.size)) - : _data.sort((a,b) => b.size.compareTo(a.size)); - return _data; - } -} diff --git a/lib/modules/sonarr/core/state.dart b/lib/modules/sonarr/core/state.dart new file mode 100644 index 00000000..4d85f9f8 --- /dev/null +++ b/lib/modules/sonarr/core/state.dart @@ -0,0 +1,392 @@ +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrState extends LunaGlobalState { + SonarrState() { + reset(); + } + + @override + void reset() { + // Reset stored data + _series = null; + _missing = null; + _upcoming = null; + _history = null; + _qualityProfiles = null; + _languageProfiles = null; + _tags = null; + _episodes = {}; + _selectedEpisodes = []; + // Reset search query fields (except the home screen) + _addSearchQuery = ''; + _releasesSearchQuery = ''; + // Reinitialize + resetProfile(); + resetSeries(); + resetUpcoming(); + resetMissing(); + resetHistory(); + resetQualityProfiles(); + resetLanguageProfiles(); + resetTags(); + notifyListeners(); + } + + /////////////// + /// PROFILE /// + /////////////// + + /// API handler instance + Sonarr _api; + Sonarr get api => _api; + + /// Is the API enabled? + bool _enabled; + bool get enabled => _enabled; + + bool _enableVersion3; + bool get enableVersion3 => _enableVersion3; + + /// Sonarr host + String _host; + String get host => _host; + + /// Sonarr API key + String _apiKey; + String get apiKey => _apiKey; + + /// Headers to attach to all requests + Map _headers; + Map get headers => _headers; + + /// Reset the profile data, reinitializes API instance + void resetProfile() { + ProfileHiveObject _profile = Database.currentProfileObject; + // Copy profile into state + _enabled = _profile.sonarrEnabled ?? false; + _enableVersion3 = _profile.sonarrVersion3 ?? false; + _host = _profile.sonarrHost ?? ''; + _apiKey = _profile.sonarrKey ?? ''; + _headers = _profile.sonarrHeaders ?? {}; + // Create the API instance if Sonarr is enabled + _api = _enabled + ? Sonarr( + host: _host, + apiKey: _apiKey, + headers: Map.from(_headers), + ) + : null; + } + + //////////////// + /// EPISODES /// + //////////////// + + Map>> _episodes = {}; + Map>> get episodes => _episodes; + void fetchEpisodes(int seriesId) { + assert(seriesId != null); + if(_api != null) + _episodes[seriesId] = _api.episode.getSeriesEpisodes(seriesId: seriesId); + notifyListeners(); + } + + List _selectedEpisodes = []; + List get selectedEpisodes => _selectedEpisodes; + set selectedEpisodes(List selectedEpisodes) { + assert(selectedEpisodes != null); + _selectedEpisodes = selectedEpisodes; + notifyListeners(); + } + + void addSelectedEpisode(int id) { + if(!_selectedEpisodes.contains(id)) _selectedEpisodes.add(id); + notifyListeners(); + } + + void removeSelectedEpisode(int id) { + if(_selectedEpisodes.contains(id)) _selectedEpisodes.remove(id); + notifyListeners(); + } + + void toggleSelectedEpisode(int id) { + _selectedEpisodes.contains(id) + ? _selectedEpisodes.remove(id) + : _selectedEpisodes.add(id); + notifyListeners(); + } + + ////////////////// + /// ADD SERIES /// + ////////////////// + + String _addSearchQuery = ''; + String get addSearchQuery => _addSearchQuery; + set addSearchQuery(String addSearchQuery) { + assert(addSearchQuery != null); + _addSearchQuery = addSearchQuery; + notifyListeners(); + } + + Future> _seriesLookup; + Future> get seriesLookup => _seriesLookup; + void fetchSeriesLookup() { + if(_api != null) + _seriesLookup = _api.seriesLookup.getSeriesLookup(term: _addSearchQuery); + notifyListeners(); + } + + Future> _rootFolders; + Future> get rootFolders => _rootFolders; + void fetchRootFolders() { + if(_api != null) + _rootFolders = _api.rootFolder.getRootFolders(); + notifyListeners(); + } + + //////////////// + /// RELEASES /// + //////////////// + + String _releasesSearchQuery = ''; + String get releasesSearchQuery => _releasesSearchQuery; + set releasesSearchQuery(String releasesSearchQuery) { + assert(releasesSearchQuery != null); + _releasesSearchQuery = releasesSearchQuery; + notifyListeners(); + } + + SonarrReleasesHiding _releasesHidingType = SonarrReleasesHiding.ALL; + SonarrReleasesHiding get releasesHidingType => _releasesHidingType; + set releasesHidingType(SonarrReleasesHiding releasesHidingType) { + assert(releasesHidingType != null); + _releasesHidingType = releasesHidingType; + notifyListeners(); + } + + SonarrReleasesSorting _releasesSortType = SonarrReleasesSorting.WEIGHT; + SonarrReleasesSorting get releasesSortType => _releasesSortType; + set releasesSortType(SonarrReleasesSorting releasesSortType) { + assert(releasesSortType != null); + _releasesSortType = releasesSortType; + notifyListeners(); + } + + bool _releasesSortAscending = true; + bool get releasesSortAscending => _releasesSortAscending; + set releasesSortAscending(bool releasesSortAscending) { + assert(releasesSortAscending != null); + _releasesSortAscending = releasesSortAscending; + notifyListeners(); + } + + ////////////// + /// SERIES /// + ////////////// + + String _seriesSearchQuery = ''; + String get seriesSearchQuery => _seriesSearchQuery; + set seriesSearchQuery(String seriesSearchQuery) { + assert(seriesSearchQuery != null); + _seriesSearchQuery = seriesSearchQuery; + notifyListeners(); + } + + SonarrSeriesSorting _seriesSortType = SonarrSeriesSorting.ALPHABETICAL; + SonarrSeriesSorting get seriesSortType => _seriesSortType; + set seriesSortType(SonarrSeriesSorting seriesSortType) { + assert(seriesSortType != null); + _seriesSortType = seriesSortType; + notifyListeners(); + } + + SonarrSeriesHiding _seriesHidingType = SonarrSeriesHiding.ALL; + SonarrSeriesHiding get seriesHidingType => _seriesHidingType; + set seriesHidingType(SonarrSeriesHiding seriesHidingType) { + assert(seriesHidingType != null); + _seriesHidingType = seriesHidingType; + notifyListeners(); + } + + bool _seriesSortAscending = true; + bool get seriesSortAscending => _seriesSortAscending; + set seriesSortAscending(bool seriesSortAscending) { + assert(seriesSortAscending != null); + _seriesSortAscending = seriesSortAscending; + notifyListeners(); + } + + Future> _series; + Future> get series => _series; + set series(Future> series) { + assert(series != null); + _series = series; + notifyListeners(); + } + + void resetSeries() { + if(_api != null) _series = _api.series.getAllSeries(); + notifyListeners(); + } + + /////////////// + /// MISSING /// + /////////////// + + Future _missing; + Future get missing => _missing; + set missing(Future missing) { + assert(missing != null); + _missing = missing; + notifyListeners(); + } + + void resetMissing() { + if(_api != null) _missing = _api.wanted.getMissing( + pageSize: SonarrDatabaseValue.CONTENT_LOAD_LENGTH.data, + sortDir: SonarrSortDirection.DESCENDING, + sortKey: SonarrWantedMissingSortKey.AIRDATE_UTC, + ); + notifyListeners(); + } + + /////////////// + /// HISTORY /// + /////////////// + + Future _history; + Future get history => _history; + set history(Future history) { + assert(history != null); + _history = history; + notifyListeners(); + } + + void resetHistory() { + if(_api != null) _history = _api.history.getHistory( + page: 1, + pageSize: SonarrDatabaseValue.CONTENT_LOAD_LENGTH.data, + sortKey: SonarrHistorySortKey.DATE, + sortDirection: SonarrSortDirection.DESCENDING, + ); + notifyListeners(); + } + + + //////////////// + /// UPCOMING /// + //////////////// + + Future> _upcoming; + Future> get upcoming => _upcoming; + set upcoming(Future> upcoming) { + assert(upcoming != null); + _upcoming = upcoming; + notifyListeners(); + } + + void resetUpcoming() { + DateTime start = DateTime.now(); + DateTime end = start.add(Duration(days: SonarrDatabaseValue.UPCOMING_FUTURE_DAYS.data)); + if(_api != null) _upcoming = _api.calendar.getCalendar( + start: start, + end: end, + ); + notifyListeners(); + } + + + //////////////// + /// PROFILES /// + //////////////// + + Future> _qualityProfiles; + Future> get qualityProfiles => _qualityProfiles; + set qualityProfiles(Future> qualityProfiles) { + assert(qualityProfiles != null); + _qualityProfiles = qualityProfiles; + notifyListeners(); + } + + void resetQualityProfiles() { + if(_api != null) _qualityProfiles = _api.profile.getQualityProfiles(); + notifyListeners(); + } + + Future> _languageProfiles; + Future> get languageProfiles => _languageProfiles; + set languageProfiles(Future> languageProfiles) { + assert(languageProfiles != null); + _languageProfiles = languageProfiles; + notifyListeners(); + } + + void resetLanguageProfiles() { + if(_api != null && _enableVersion3) _languageProfiles = _api.profile.getLanguageProfiles(); + notifyListeners(); + } + + //////////// + /// TAGS /// + //////////// + + Future> _tags; + Future> get tags => _tags; + set tags(Future> tags) { + assert(tags != null); + _tags = tags; + notifyListeners(); + } + + void resetTags() { + if(_api != null) _tags = _api.tag.getTags(); + notifyListeners(); + } + + ///////////////////// + /// DELETE SERIES /// + ///////////////////// + + bool _removeSeriesDeleteFiles = false; + bool get removeSeriesDeleteFiles => _removeSeriesDeleteFiles; + set removeSeriesDeleteFiles(bool removeSeriesDeleteFiles) { + assert(removeSeriesDeleteFiles != null); + _removeSeriesDeleteFiles = removeSeriesDeleteFiles; + notifyListeners(); + } + + ////////////// + /// IMAGES /// + ////////////// + + String getBannerURL(int seriesId, { bool highRes = false }) { + if(_enabled) { + String _base = _host.endsWith('/') ? '${_host}api/MediaCover' : '$_host/api/MediaCover'; + return highRes + ? '$_base/$seriesId/banner.jpg?apikey=$_apiKey' + : '$_base/$seriesId/banner-70.jpg?apikey=$_apiKey'; + } + return null; + } + + String getPosterURL(int seriesId, { bool highRes = false }) { + if(_enabled) { + String _base = _host.endsWith('/') ? '${_host}api/MediaCover' : '$_host/api/MediaCover'; + return highRes + ? '$_base/$seriesId/poster.jpg?apikey=$_apiKey' + : '$_base/$seriesId/poster-500.jpg?apikey=$_apiKey'; + } + return null; + } + + String getFanartURL(int seriesId, { bool highRes = false }) { + if(_enabled) { + String _base = _host.endsWith('/') ? '${_host}api/MediaCover' : '$_host/api/MediaCover'; + return highRes + ? '$_base/$seriesId/fanart.jpg?apikey=$_apiKey' + : '$_base/$seriesId/fanart-360.jpg?apikey=$_apiKey'; + } + return null; + } +} diff --git a/lib/modules/sonarr/core/types.dart b/lib/modules/sonarr/core/types.dart new file mode 100644 index 00000000..72ccd11e --- /dev/null +++ b/lib/modules/sonarr/core/types.dart @@ -0,0 +1,9 @@ +export 'types/hiding_releases.dart'; +export 'types/hiding_series.dart'; +export 'types/monitor_status.dart'; +export 'types/settings_episode.dart'; +export 'types/settings_global.dart'; +export 'types/settings_season.dart'; +export 'types/settings_series.dart'; +export 'types/sorting_releases.dart'; +export 'types/sorting_series.dart'; diff --git a/lib/modules/sonarr/core/types/hiding_releases.dart b/lib/modules/sonarr/core/types/hiding_releases.dart new file mode 100644 index 00000000..9cd9c013 --- /dev/null +++ b/lib/modules/sonarr/core/types/hiding_releases.dart @@ -0,0 +1,48 @@ +import 'package:lunasea/modules/sonarr.dart'; + +enum SonarrReleasesHiding { + ALL, + APPROVED, + REJECTED, +} + +extension SonarrReleasesHidingExtension on SonarrReleasesHiding { + static _Sorter _sorter = _Sorter(); + + String get value { + switch(this) { + case SonarrReleasesHiding.ALL: return 'all'; + case SonarrReleasesHiding.APPROVED: return 'approved'; + case SonarrReleasesHiding.REJECTED: return 'rejected'; + } + throw Exception('value not found'); + } + + String get readable { + switch(this) { + case SonarrReleasesHiding.ALL: return 'All'; + case SonarrReleasesHiding.APPROVED: return 'Approved'; + case SonarrReleasesHiding.REJECTED: return 'Rejected'; + } + throw Exception('readable not found'); + } + + List filter(List releases) => _sorter.byType(releases, this); +} + +class _Sorter { + List byType( + List releases, + SonarrReleasesHiding type, + ) { + switch(type) { + case SonarrReleasesHiding.ALL: return releases; + case SonarrReleasesHiding.APPROVED: return _approved(releases); + case SonarrReleasesHiding.REJECTED: return _rejected(releases); + } + throw Exception('sorting type not found'); + } + + List _approved(List releases) => releases.where((element) => element.approved).toList(); + List _rejected(List releases) => releases.where((element) => !element.approved).toList(); +} diff --git a/lib/modules/sonarr/core/types/hiding_series.dart b/lib/modules/sonarr/core/types/hiding_series.dart new file mode 100644 index 00000000..2d505009 --- /dev/null +++ b/lib/modules/sonarr/core/types/hiding_series.dart @@ -0,0 +1,49 @@ +import 'package:lunasea/modules/sonarr.dart'; + +enum SonarrSeriesHiding { + ALL, + MONITORED, + UNMONITORED, +} + +extension SonarrSeriesHidingExtension on SonarrSeriesHiding { + static _Sorter _sorter = _Sorter(); + + String get value { + switch(this) { + case SonarrSeriesHiding.ALL: return 'all'; + case SonarrSeriesHiding.MONITORED: return 'monitored'; + case SonarrSeriesHiding.UNMONITORED: return 'unmonitored'; + } + throw Exception('value not found'); + } + + String get readable { + switch(this) { + case SonarrSeriesHiding.ALL: return 'All'; + case SonarrSeriesHiding.MONITORED: return 'Monitored'; + case SonarrSeriesHiding.UNMONITORED: return 'Unmonitored'; + } + throw Exception('readable not found'); + } + + List filter(List series) => _sorter.byType(series, this); +} + +class _Sorter { + List byType( + List series, + SonarrSeriesHiding type, + ) { + switch(type) { + case SonarrSeriesHiding.ALL: return series; + case SonarrSeriesHiding.MONITORED: return _monitored(series); + case SonarrSeriesHiding.UNMONITORED: return _unmonitored(series); + } + throw Exception('sorting type not found'); + } + + List _monitored(List series) => series.where((element) => element.monitored).toList(); + + List _unmonitored(List series) => series.where((element) => !element.monitored).toList(); +} diff --git a/lib/modules/sonarr/core/types/monitor_status.dart b/lib/modules/sonarr/core/types/monitor_status.dart new file mode 100644 index 00000000..1f5e4702 --- /dev/null +++ b/lib/modules/sonarr/core/types/monitor_status.dart @@ -0,0 +1,72 @@ +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; +part 'monitor_status.g.dart'; + +@HiveType(typeId: 14, adapterName: 'SonarrMonitorStatusAdapter') +enum SonarrMonitorStatus { + @HiveField(0) + ALL, + @HiveField(1) + FUTURE, + @HiveField(2) + MISSING, + @HiveField(3) + EXISTING, + @HiveField(4) + FIRST_SEASON, + @HiveField(5) + LAST_SEASON, + @HiveField(6) + NONE, +} + +extension SonarrMonitorStatusExtension on SonarrMonitorStatus { + String get name { + switch(this) { + case SonarrMonitorStatus.ALL: return 'All'; + case SonarrMonitorStatus.MISSING: return 'Missing'; + case SonarrMonitorStatus.EXISTING: return 'Existing'; + case SonarrMonitorStatus.FIRST_SEASON: return 'First Season'; + case SonarrMonitorStatus.LAST_SEASON: return 'Last Season'; + case SonarrMonitorStatus.NONE: return 'None'; + case SonarrMonitorStatus.FUTURE: return 'Future'; + } + throw Exception('unknown name'); + } + + void process(List season) { + switch(this) { + case SonarrMonitorStatus.ALL: _all(season); break; + case SonarrMonitorStatus.MISSING: _missing(season); break; + case SonarrMonitorStatus.EXISTING: _existing(season); break; + case SonarrMonitorStatus.FIRST_SEASON: _firstSeason(season); break; + case SonarrMonitorStatus.LAST_SEASON: _lastSeason(season); break; + case SonarrMonitorStatus.NONE: _none(season); break; + case SonarrMonitorStatus.FUTURE: _future(season); break; + } + } + + void _all(List data) => data.forEach((season) { + if(season.seasonNumber != 0) season.monitored = true; + }); + + void _missing(List data) => _all(data); + + void _existing(List data) => _all(data); + + void _future(List data) => _lastSeason(data); + + void _firstSeason(List data) { + _none(data); + data[0].seasonNumber == 0 + ? data[1].monitored = true + : data[0].monitored = true; + } + + void _lastSeason(List data) { + _none(data); + data[data.length-1].monitored = true; + } + + void _none(List data) => data.forEach((season) => season.monitored = false); +} diff --git a/lib/modules/sonarr/core/api/data/monitor_status.g.dart b/lib/modules/sonarr/core/types/monitor_status.g.dart similarity index 100% rename from lib/modules/sonarr/core/api/data/monitor_status.g.dart rename to lib/modules/sonarr/core/types/monitor_status.g.dart diff --git a/lib/modules/sonarr/core/types/settings_episode.dart b/lib/modules/sonarr/core/types/settings_episode.dart new file mode 100644 index 00000000..402841ec --- /dev/null +++ b/lib/modules/sonarr/core/types/settings_episode.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +enum SonarrEpisodeSettingsType { + MONITORED, + AUTOMATIC_SEARCH, + INTERACTIVE_SEARCH, + DELETE_FILE, +} + +extension SonarrEpisodeSettingsTypeExtension on SonarrEpisodeSettingsType { + IconData icon(SonarrEpisode episode) { + switch(this) { + case SonarrEpisodeSettingsType.MONITORED: return episode.monitored ? Icons.turned_in_not : Icons.turned_in; + case SonarrEpisodeSettingsType.AUTOMATIC_SEARCH: return Icons.search; + case SonarrEpisodeSettingsType.INTERACTIVE_SEARCH: return Icons.youtube_searched_for; + case SonarrEpisodeSettingsType.DELETE_FILE: return Icons.delete; + } + throw Exception('Invalid SonarrEpisodeSettingsType'); + } + + String name(SonarrEpisode episode) { + switch(this) { + case SonarrEpisodeSettingsType.MONITORED: return episode.monitored ? 'Unmonitor Episode' : 'Monitor Episode'; + case SonarrEpisodeSettingsType.AUTOMATIC_SEARCH: return 'Automatic Search'; + case SonarrEpisodeSettingsType.INTERACTIVE_SEARCH: return 'Interactive Search'; + case SonarrEpisodeSettingsType.DELETE_FILE: return 'Delete File'; + } + throw Exception('Invalid SonarrEpisodeSettingsType'); + } +} diff --git a/lib/modules/sonarr/core/types/settings_global.dart b/lib/modules/sonarr/core/types/settings_global.dart new file mode 100644 index 00000000..1439e6a4 --- /dev/null +++ b/lib/modules/sonarr/core/types/settings_global.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; + +enum SonarrGlobalSettingsType { + WEB_GUI, + MANAGE_TAGS, + VIEW_QUEUE, + RUN_RSS_SYNC, + SEARCH_ALL_MISSING, + UPDATE_LIBRARY, + BACKUP_DATABASE, +} + +extension SonarrGlobalSettingsTypeExtension on SonarrGlobalSettingsType { + IconData get icon { + switch(this) { + case SonarrGlobalSettingsType.WEB_GUI: return Icons.language; + case SonarrGlobalSettingsType.VIEW_QUEUE: return Icons.queue; + case SonarrGlobalSettingsType.MANAGE_TAGS: return Icons.style; + case SonarrGlobalSettingsType.UPDATE_LIBRARY: return Icons.autorenew; + case SonarrGlobalSettingsType.RUN_RSS_SYNC: return Icons.rss_feed; + case SonarrGlobalSettingsType.SEARCH_ALL_MISSING: return Icons.search; + case SonarrGlobalSettingsType.BACKUP_DATABASE: return Icons.save; + } + throw Exception('Invalid SonarrGlobalSettingsType'); + } + + String get name { + switch(this) { + case SonarrGlobalSettingsType.WEB_GUI: return 'View Web GUI'; + case SonarrGlobalSettingsType.VIEW_QUEUE: return 'View Queue'; + case SonarrGlobalSettingsType.MANAGE_TAGS: return 'Manage Tags'; + case SonarrGlobalSettingsType.UPDATE_LIBRARY: return 'Update Library'; + case SonarrGlobalSettingsType.RUN_RSS_SYNC: return 'Run RSS Sync'; + case SonarrGlobalSettingsType.SEARCH_ALL_MISSING: return 'Search All Missing'; + case SonarrGlobalSettingsType.BACKUP_DATABASE: return 'Backup Database'; + } + throw Exception('Invalid SonarrGlobalSettingsType'); + } +} diff --git a/lib/modules/sonarr/core/types/settings_season.dart b/lib/modules/sonarr/core/types/settings_season.dart new file mode 100644 index 00000000..ce5795f9 --- /dev/null +++ b/lib/modules/sonarr/core/types/settings_season.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; + +enum SonarrSeasonSettingsType { + AUTOMATIC_SEARCH, + INTERACTIVE_SEARCH, +} + +extension SonarrSeasonSettingsTypeExtension on SonarrSeasonSettingsType { + IconData get icon { + switch(this) { + case SonarrSeasonSettingsType.AUTOMATIC_SEARCH: return Icons.search; + case SonarrSeasonSettingsType.INTERACTIVE_SEARCH: return Icons.youtube_searched_for; + } + throw Exception('Invalid SonarrSeasonSettingsType'); + } + + String get name { + switch(this) { + case SonarrSeasonSettingsType.AUTOMATIC_SEARCH: return 'Automatic Search'; + case SonarrSeasonSettingsType.INTERACTIVE_SEARCH: return 'Interactive Search'; + } + throw Exception('Invalid SonarrSeasonSettingsType'); + } +} diff --git a/lib/modules/sonarr/core/types/settings_series.dart b/lib/modules/sonarr/core/types/settings_series.dart new file mode 100644 index 00000000..1546fe6b --- /dev/null +++ b/lib/modules/sonarr/core/types/settings_series.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +enum SonarrSeriesSettingsType { + EDIT, + REFRESH, + DELETE, + MONITORED, +} + +extension SonarrSeriesSettingsTypeExtension on SonarrSeriesSettingsType { + IconData icon(SonarrSeries series) { + switch(this) { + case SonarrSeriesSettingsType.MONITORED: return series.monitored ? Icons.turned_in_not : Icons.turned_in; + case SonarrSeriesSettingsType.EDIT: return Icons.edit; + case SonarrSeriesSettingsType.REFRESH: return Icons.refresh; + case SonarrSeriesSettingsType.DELETE: return Icons.delete; + } + throw Exception('Invalid SonarrSeriesSettingsType'); + } + + String name(SonarrSeries series) { + switch(this) { + case SonarrSeriesSettingsType.MONITORED: return series.monitored ? 'Unmonitor Series' : 'Monitor Series'; + case SonarrSeriesSettingsType.EDIT: return 'Edit Series'; + case SonarrSeriesSettingsType.REFRESH: return 'Refresh Series'; + case SonarrSeriesSettingsType.DELETE: return 'Remove Series'; + } + throw Exception('Invalid SonarrSeriesSettingsType'); + } +} diff --git a/lib/modules/sonarr/core/types/sorting_releases.dart b/lib/modules/sonarr/core/types/sorting_releases.dart new file mode 100644 index 00000000..a9ace908 --- /dev/null +++ b/lib/modules/sonarr/core/types/sorting_releases.dart @@ -0,0 +1,106 @@ +import 'package:lunasea/modules/sonarr.dart'; + +enum SonarrReleasesSorting { + AGE, + ALPHABETICAL, + SEEDERS, + SIZE, + TYPE, + WEIGHT, +} + +extension SonarrReleasesSortingExtension on SonarrReleasesSorting { + static _Sorter _sorter = _Sorter(); + + String get value { + switch(this) { + case SonarrReleasesSorting.AGE: return 'age'; + case SonarrReleasesSorting.ALPHABETICAL: return 'abc'; + case SonarrReleasesSorting.SEEDERS: return 'seeders'; + case SonarrReleasesSorting.WEIGHT: return 'weight'; + case SonarrReleasesSorting.TYPE: return 'type'; + case SonarrReleasesSorting.SIZE: return 'size'; + } + throw Exception('value not found'); + } + + String get readable { + switch(this) { + case SonarrReleasesSorting.AGE: return 'Age'; + case SonarrReleasesSorting.ALPHABETICAL: return 'Alphabetical'; + case SonarrReleasesSorting.SEEDERS: return 'Seeders'; + case SonarrReleasesSorting.WEIGHT: return 'Weight'; + case SonarrReleasesSorting.TYPE: return 'Type'; + case SonarrReleasesSorting.SIZE: return 'Size'; + } + throw Exception('readable not found'); + } + + List sort( + List releases, + bool ascending + ) => _sorter.byType(releases, this, ascending); +} + +class _Sorter { + List byType( + List releases, + SonarrReleasesSorting type, + bool ascending, + ) { + switch(type) { + case SonarrReleasesSorting.AGE: return _age(releases, ascending); + case SonarrReleasesSorting.ALPHABETICAL: return _alphabetical(releases, ascending); + case SonarrReleasesSorting.SEEDERS: return _seeders(releases, ascending); + case SonarrReleasesSorting.WEIGHT: return _weight(releases, ascending); + case SonarrReleasesSorting.TYPE: return _type(releases, ascending); + case SonarrReleasesSorting.SIZE: return _size(releases, ascending); + } + throw Exception('sorting type not found'); + } + + List _alphabetical(List releases, bool ascending) { + ascending + ? releases.sort((a,b) => a.title.toLowerCase().compareTo(b.title.toLowerCase())) + : releases.sort((a,b) => b.title.toLowerCase().compareTo(a.title.toLowerCase())); + return releases; + } + + List _age(List releases, bool ascending) { + ascending + ? releases.sort((a,b) => a.ageHours.compareTo(b.ageHours)) + : releases.sort((a,b) => b.ageHours.compareTo(a.ageHours)); + return releases; + } + + List _seeders(List releases, bool ascending) { + List _torrent = _weight(releases.where((release) => release.protocol == 'torrent').toList(), true); + List _usenet = _weight(releases.where((release) => release.protocol == 'usenet').toList(), true); + ascending + ? _torrent.sort((a,b) => (a.seeders ?? -1).compareTo((b.seeders ?? -1))) + : _torrent.sort((a,b) => (b.seeders ?? -1).compareTo((a.seeders ?? -1))); + return [..._torrent, ..._usenet]; + } + + List _weight(List releases, bool ascending) { + ascending + ? releases.sort((a,b) => (a.releaseWeight ?? -1).compareTo((b.releaseWeight ?? -1))) + : releases.sort((a,b) => (b.releaseWeight ?? -1).compareTo((a.releaseWeight ?? -1))); + return releases; + } + + List _type(List releases, bool ascending) { + List _torrent = _weight(releases.where((release) => release.protocol == 'torrent').toList(), true); + List _usenet = _weight(releases.where((release) => release.protocol == 'usenet').toList(), true); + return ascending + ? [..._torrent, ..._usenet] + : [..._usenet, ..._torrent]; + } + + List _size(List releases, bool ascending) { + ascending + ? releases.sort((a,b) => (a.size ?? -1).compareTo((b.size ?? -1))) + : releases.sort((a,b) => (b.size ?? -1).compareTo((a.size ?? -1))); + return releases; + } +} diff --git a/lib/modules/sonarr/core/types/sorting_series.dart b/lib/modules/sonarr/core/types/sorting_series.dart new file mode 100644 index 00000000..20cf156f --- /dev/null +++ b/lib/modules/sonarr/core/types/sorting_series.dart @@ -0,0 +1,174 @@ +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +enum SonarrSeriesSorting { + ALPHABETICAL, + DATE_ADDED, + EPISODES, + NETWORK, + NEXT_AIRING, + QUALITY, + SIZE, + TYPE, +} + +extension SonarrSeriesSortingExtension on SonarrSeriesSorting { + static _Sorter _sorter = _Sorter(); + + String get value { + switch(this) { + case SonarrSeriesSorting.ALPHABETICAL: return 'abc'; + case SonarrSeriesSorting.DATE_ADDED: return 'date_added'; + case SonarrSeriesSorting.EPISODES: return 'episodes'; + case SonarrSeriesSorting.SIZE: return 'size'; + case SonarrSeriesSorting.TYPE: return 'type'; + case SonarrSeriesSorting.NETWORK: return 'network'; + case SonarrSeriesSorting.QUALITY: return 'quality'; + case SonarrSeriesSorting.NEXT_AIRING: return 'next_airing'; + } + throw Exception('value not found'); + } + + String get readable { + switch(this) { + case SonarrSeriesSorting.ALPHABETICAL: return 'Alphabetical'; + case SonarrSeriesSorting.DATE_ADDED: return 'Date Added'; + case SonarrSeriesSorting.EPISODES: return 'Episodes'; + case SonarrSeriesSorting.NETWORK: return 'Network'; + case SonarrSeriesSorting.SIZE: return 'Size'; + case SonarrSeriesSorting.TYPE: return 'Type'; + case SonarrSeriesSorting.QUALITY: return 'Quality Profile'; + case SonarrSeriesSorting.NEXT_AIRING: return 'Next Airing'; + } + throw Exception('readable not found'); + } + + List sort(List data, bool ascending) => _sorter.byType(data, this, ascending); +} + +class _Sorter { + List byType( + List data, + SonarrSeriesSorting type, + bool ascending, + ) { + switch(type) { + case SonarrSeriesSorting.DATE_ADDED: return _dateAdded(data, ascending); + case SonarrSeriesSorting.EPISODES: return _episodes(data, ascending); + case SonarrSeriesSorting.NETWORK: return _network(data, ascending); + case SonarrSeriesSorting.NEXT_AIRING: return _nextAiring(data, ascending); + case SonarrSeriesSorting.SIZE: return _size(data, ascending); + case SonarrSeriesSorting.TYPE: return _type(data, ascending); + case SonarrSeriesSorting.ALPHABETICAL: return _alphabetical(data, ascending); + case SonarrSeriesSorting.QUALITY: return _quality(data, ascending); + } + throw Exception('sorting type not found'); + } + + List _alphabetical(List series, bool ascending) { + ascending + ? series.sort((a,b) => a.sortTitle.toLowerCase().compareTo(b.sortTitle.toLowerCase())) + : series.sort((a,b) => b.sortTitle.toLowerCase().compareTo(a.sortTitle.toLowerCase())); + return series; + } + + List _dateAdded(List series, bool ascending) { + series.sort((a,b) { + if(ascending) { + if(a.added == null) return 1; + if(b.added == null) return -1; + int _comparison = a.added.compareTo(b.added); + return _comparison == 0 + ? a.sortTitle.toLowerCase().compareTo(b.sortTitle.toLowerCase()) + : _comparison; + } else { + if(b.added == null) return -1; + if(a.added == null) return 1; + int _comparison = b.added.compareTo(a.added); + return _comparison == 0 + ? a.sortTitle.toLowerCase().compareTo(b.sortTitle.toLowerCase()) + : _comparison; + } + }); + return series; + } + + List _episodes(List series, bool ascending) { + series.sort((a,b) { + int _comparison = ascending + ? (a.lunaPercentageComplete ?? 0).compareTo(b.lunaPercentageComplete ?? 0) + : (b.lunaPercentageComplete ?? 0).compareTo(a.lunaPercentageComplete ?? 0); + return _comparison == 0 + ? a.sortTitle.toLowerCase().compareTo(b.sortTitle.toLowerCase()) + : _comparison; + }); + return series; + } + + List _network(List series, bool ascending) { + series.sort((a,b) { + int _comparison = ascending + ? (a.network ?? Constants.TEXT_EMDASH).compareTo((b.network ?? Constants.TEXT_EMDASH)) + : (b.network ?? Constants.TEXT_EMDASH).compareTo((a.network ?? Constants.TEXT_EMDASH)); + return _comparison == 0 + ? a.sortTitle.toLowerCase().compareTo(b.sortTitle.toLowerCase()) + : _comparison; + }); + return series; + } + + List _nextAiring(List series, bool ascending) { + series.sort((a,b) { + if(ascending) { + if(a.nextAiring == null) return 1; + if(b.nextAiring == null) return -1; + int _comparison = a.nextAiring.compareTo(b.nextAiring); + return _comparison == 0 + ? a.sortTitle.toLowerCase().compareTo(b.sortTitle.toLowerCase()) + : _comparison; + } else { + if(b.nextAiring == null) return -1; + if(a.nextAiring == null) return 1; + int _comparison = b.nextAiring.compareTo(a.nextAiring); + return _comparison == 0 + ? a.sortTitle.toLowerCase().compareTo(b.sortTitle.toLowerCase()) + : _comparison; + } + }); + return series; + } + + List _quality(List series, bool ascending) { + series.sort((a,b) { + int _comparison = ascending + ? (a.qualityProfileId ?? 0).compareTo(b.qualityProfileId ?? 0) + : (b.qualityProfileId ?? 0).compareTo(a.qualityProfileId ?? 0); + return _comparison == 0 + ? a.sortTitle.toLowerCase().compareTo(b.sortTitle.toLowerCase()) + : _comparison; + }); + return series; + } + + List _size(List series, bool ascending) { + series.sort((a,b) { + int _comparison = ascending + ? (a.sizeOnDisk ?? 0).compareTo(b.sizeOnDisk ?? 0) + : (b.sizeOnDisk ?? 0).compareTo(a.sizeOnDisk ?? 0); + return _comparison == 0 + ? a.sortTitle.toLowerCase().compareTo(b.sortTitle.toLowerCase()) + : _comparison; + }); + return series; + } + + List _type(List series, bool ascending) { + List _anime = series.where((element) => element.seriesType == SonarrSeriesType.ANIME).toList(); + _anime.sort((a,b) => a.sortTitle.toLowerCase().compareTo(b.sortTitle.toLowerCase())); + List _daily = series.where((element) => element.seriesType == SonarrSeriesType.DAILY).toList(); + _daily.sort((a,b) => a.sortTitle.toLowerCase().compareTo(b.sortTitle.toLowerCase())); + List _stand = series.where((element) => element.seriesType == SonarrSeriesType.STANDARD).toList(); + _stand.sort((a,b) => a.sortTitle.toLowerCase().compareTo(b.sortTitle.toLowerCase())); + return ascending ? [..._anime, ..._daily, ..._stand] : [..._stand, ..._daily, ..._anime]; + } +} diff --git a/lib/modules/sonarr/modules.dart b/lib/modules/sonarr/modules.dart new file mode 100644 index 00000000..aef5ca13 --- /dev/null +++ b/lib/modules/sonarr/modules.dart @@ -0,0 +1,13 @@ +export 'modules/history.dart'; +export 'modules/missing.dart'; +export 'modules/queue.dart'; +export 'modules/releases.dart'; +export 'modules/series.dart'; +export 'modules/series_add.dart'; +export 'modules/series_add_details.dart'; +export 'modules/series_details.dart'; +export 'modules/series_edit.dart'; +export 'modules/series_season_details.dart'; +export 'modules/sonarr.dart'; +export 'modules/tags.dart'; +export 'modules/upcoming.dart'; diff --git a/lib/modules/sonarr/modules/history.dart b/lib/modules/sonarr/modules/history.dart new file mode 100644 index 00000000..a89dfef1 --- /dev/null +++ b/lib/modules/sonarr/modules/history.dart @@ -0,0 +1,2 @@ +export 'history/route.dart'; +export 'history/widgets.dart'; diff --git a/lib/modules/sonarr/modules/history/route.dart b/lib/modules/sonarr/modules/history/route.dart new file mode 100644 index 00000000..6da75a77 --- /dev/null +++ b/lib/modules/sonarr/modules/history/route.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrHistoryRoute extends StatefulWidget { + @override + State createState() => _State(); +} + +class _State extends State with AutomaticKeepAliveClientMixin { + final GlobalKey _scaffoldKey = GlobalKey(); + final GlobalKey _refreshKey = GlobalKey(); + + @override + bool get wantKeepAlive => true; + + @override + void initState() { + super.initState(); + SchedulerBinding.instance.scheduleFrameCallback((_) => _refresh()); + } + + Future _refresh() async { + SonarrState _state = Provider.of(context, listen: false); + _state.resetHistory(); + await _state.history; + } + + @override + Widget build(BuildContext context) { + super.build(context); + return Scaffold( + key: _scaffoldKey, + body: _body, + ); + } + + Widget get _body => LSRefreshIndicator( + refreshKey: _refreshKey, + onRefresh: _refresh, + child: Selector>( + selector: (_, state) => state.history, + builder: (context, future, _) => FutureBuilder( + future: future, + builder: (context, AsyncSnapshot snapshot) { + if(snapshot.hasError) { + if(snapshot.connectionState != ConnectionState.waiting) { + LunaLogger.error( + '_SonarrHistoryRoute', + '_body', + 'Unable to fetch Sonarr history', + snapshot.error, + null, + uploadToSentry: !(snapshot.error is DioError), + ); + } + return LSErrorMessage(onTapHandler: () async => _refreshKey.currentState.show()); + } + if(snapshot.hasData) { + return snapshot.data.records.length == 0 + ? _noHistory() + : _history(snapshot.data); + } + return LSLoader(); + }, + ), + ), + ); + + Widget _noHistory() => LSGenericMessage( + text: 'No History Found', + showButton: true, + buttonText: 'Refresh', + onTapHandler: () async => _refreshKey.currentState.show(), + ); + + Widget _history(SonarrHistory history) => LSListView( + children: List.generate( + history.records.length, + (index) => SonarrHistoryTile(record: history.records[index]), + ), + ); +} \ No newline at end of file diff --git a/lib/modules/sonarr/modules/history/widgets.dart b/lib/modules/sonarr/modules/history/widgets.dart new file mode 100644 index 00000000..54a3049e --- /dev/null +++ b/lib/modules/sonarr/modules/history/widgets.dart @@ -0,0 +1 @@ +export 'widgets/history_tile.dart'; diff --git a/lib/modules/sonarr/modules/history/widgets/history_tile.dart b/lib/modules/sonarr/modules/history/widgets/history_tile.dart new file mode 100644 index 00000000..729f2d57 --- /dev/null +++ b/lib/modules/sonarr/modules/history/widgets/history_tile.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrHistoryTile extends StatefulWidget { + final SonarrHistoryRecord record; + + SonarrHistoryTile({ + Key key, + @required this.record, + }) : super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State { + final double _height = 90.0; + final double _padding = 8.0; + + @override + Widget build(BuildContext context) => LSCard( + child: InkWell( + child: Row( + children: [ + Expanded(child: _information), + ], + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + ), + onTap: _onTap, + borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), + ), + ); + + Widget get _information => Padding( + child: Container( + child: Column( + children: [ + LSTitle(text: widget.record.series.title, maxLines: 1), + _subtitleOne, + _subtitleTwo, + _subtitleThree, + ], + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + ), + height: (_height-(_padding*2)), + ), + padding: EdgeInsets.symmetric(vertical: _padding, horizontal: _padding+4.0), + ); + + Widget get _subtitleOne => RichText( + text: TextSpan( + style: TextStyle( + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + color: Colors.white70, + ), + children: [ + TextSpan(text: 'Season ${widget.record.episode.seasonNumber} '), + TextSpan(text: 'Episode ${widget.record.episode.episodeNumber}'), + ], + ), + overflow: TextOverflow.fade, + softWrap: false, + maxLines: 1, + ); + + Widget get _subtitleTwo => RichText( + text: TextSpan( + style: TextStyle( + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + color: Colors.white70, + ), + children: [ + TextSpan( + text: DateTime.now().toLocal().lsDateTime_ageString(widget.record.date), + ), + ], + ), + overflow: TextOverflow.fade, + softWrap: false, + maxLines: 1, + ); + + Widget get _subtitleThree => RichText( + text: TextSpan( + style: TextStyle( + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + color: widget.record.eventType.lunaColour, + fontWeight: FontWeight.w600, + ), + text: widget.record.eventType.lunaMessage(widget.record), + ), + overflow: TextOverflow.fade, + softWrap: false, + maxLines: 1, + ); + + TextSpan get seasonEpisode => TextSpan( + text: 'Season ${widget.record.episode.seasonNumber} Episode ${widget.record.episode.episodeNumber}', + ); + + + Future _onTap() async => SonarrSeriesDetailsRouter.navigateTo( + context, + seriesId: widget.record.seriesId, + ); +} diff --git a/lib/modules/sonarr/modules/missing.dart b/lib/modules/sonarr/modules/missing.dart new file mode 100644 index 00000000..1ef0049e --- /dev/null +++ b/lib/modules/sonarr/modules/missing.dart @@ -0,0 +1,2 @@ +export 'missing/route.dart'; +export 'missing/widgets.dart'; diff --git a/lib/modules/sonarr/modules/missing/route.dart b/lib/modules/sonarr/modules/missing/route.dart new file mode 100644 index 00000000..8d96b773 --- /dev/null +++ b/lib/modules/sonarr/modules/missing/route.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrMissingRoute extends StatefulWidget { + @override + State createState() => _State(); +} + +class _State extends State with AutomaticKeepAliveClientMixin { + final GlobalKey _scaffoldKey = GlobalKey(); + final GlobalKey _refreshKey = GlobalKey(); + + @override + bool get wantKeepAlive => true; + + @override + void initState() { + super.initState(); + SchedulerBinding.instance.scheduleFrameCallback((_) => _refresh()); + } + + Future _refresh() async { + SonarrState _state = Provider.of(context, listen: false); + _state.resetMissing(); + await _state.missing; + } + + @override + Widget build(BuildContext context) { + super.build(context); + return Scaffold( + key: _scaffoldKey, + body: _body, + ); + } + + Widget get _body => LSRefreshIndicator( + refreshKey: _refreshKey, + onRefresh: _refresh, + child: Selector>( + selector: (_, state) => state.missing, + builder: (context, future, _) => FutureBuilder( + future: future, + builder: (context, AsyncSnapshot snapshot) { + if(snapshot.hasError) { + if(snapshot.connectionState != ConnectionState.waiting) { + LunaLogger.error( + '_SonarrMissingRoute', + '_body', + 'Unable to fetch Sonarr missing episodes', + snapshot.error, + null, + uploadToSentry: !(snapshot.error is DioError), + ); + } + return LSErrorMessage(onTapHandler: () async => _refreshKey.currentState.show()); + } + if(snapshot.hasData) { + return snapshot.data.records.length == 0 + ? _noEpisodes() + : _episodes(snapshot.data); + } + return LSLoader(); + }, + ), + ), + ); + + Widget _noEpisodes() => LSGenericMessage( + text: 'No Episodes Found', + showButton: true, + buttonText: 'Refresh', + onTapHandler: () async => _refreshKey.currentState.show(), + ); + + Widget _episodes(SonarrMissing missing) => LSListView( + children: List.generate( + missing.records.length, + (index) => SonarrMissingTile(record: missing.records[index]), + ), + ); +} \ No newline at end of file diff --git a/lib/modules/sonarr/modules/missing/widgets.dart b/lib/modules/sonarr/modules/missing/widgets.dart new file mode 100644 index 00000000..c7f5c93d --- /dev/null +++ b/lib/modules/sonarr/modules/missing/widgets.dart @@ -0,0 +1 @@ +export 'widgets/missing_tile.dart'; diff --git a/lib/modules/sonarr/modules/missing/widgets/missing_tile.dart b/lib/modules/sonarr/modules/missing/widgets/missing_tile.dart new file mode 100644 index 00000000..7ae08dc1 --- /dev/null +++ b/lib/modules/sonarr/modules/missing/widgets/missing_tile.dart @@ -0,0 +1,180 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrMissingTile extends StatefulWidget { + final SonarrMissingRecord record; + + SonarrMissingTile({ + Key key, + @required this.record, + }) : super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State { + final double _height = 90.0; + final double _width = 60.0; + final double _padding = 8.0; + + @override + Widget build(BuildContext context) => Selector>( + selector: (_, state) => state.missing, + builder: (context, series, _) => LSCard( + child: InkWell( + child: Row( + children: [ + _poster, + Expanded(child: _information), + _trailing, + ], + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + ), + borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), + onTap: _tileOnTap, + onLongPress: _tileOnLongPress, + ), + decoration: LSCardBackground( + uri: Provider.of(context, listen: false).getBannerURL(widget.record.seriesId), + headers: Provider.of(context, listen: false).headers, + ), + ), + ); + + Widget get _poster => LSNetworkImage( + url: Provider.of(context, listen: false).getPosterURL(widget.record.seriesId), + placeholder: 'assets/images/sonarr/noseriesposter.png', + height: _height, + width: _width, + headers: Provider.of(context, listen: false).headers.cast(), + ); + + Widget get _information => Padding( + child: Container( + child: Column( + children: [ + LSTitle(text: widget.record.series.title, darken: !widget.record.monitored, maxLines: 1), + _subtitleOne, + _subtitleTwo, + _subtitleThree, + ], + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + ), + height: (_height-(_padding*2)), + ), + padding: EdgeInsets.all(_padding), + ); + + Widget get _trailing => Container( + child: Padding( + child: LSIconButton( + icon: Icons.search, + onPressed: _trailingOnPressed, + onLongPress: _trailingOnLongPress, + ), + padding: EdgeInsets.only(right: 12.0), + ), + height: _height, + ); + + Widget get _subtitleOne => RichText( + text: TextSpan( + style: TextStyle( + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + color: widget.record.monitored ? Colors.white70 : Colors.white30, + ), + children: [ + TextSpan(text: widget.record.seasonNumber == 0 ? 'Specials ' : 'Season ${widget.record.seasonNumber} '), + TextSpan(text: Constants.TEXT_EMDASH), + TextSpan(text: ' Episode ${widget.record.episodeNumber}'), + ], + ), + overflow: TextOverflow.fade, + softWrap: false, + maxLines: 1, + ); + + Widget get _subtitleTwo => RichText( + text: TextSpan( + style: TextStyle( + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + color: widget.record.monitored ? Colors.white70 : Colors.white30, + ), + children: [ + TextSpan( + style: TextStyle( + fontStyle: FontStyle.italic, + ), + text: widget.record.title ?? 'Unknown Title', + ), + ], + ), + overflow: TextOverflow.fade, + softWrap: false, + maxLines: 1, + ); + + Widget get _subtitleThree => RichText( + text: TextSpan( + style: TextStyle( + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + color: LunaColours.red, + fontWeight: FontWeight.w600, + ), + children: [ + TextSpan(text: widget.record.airDateUtc == null + ? 'Aired' + : 'Aired ${DateTime.now().lsDateTime_ageString(widget.record.airDateUtc?.toLocal())}'), + ], + ), + overflow: TextOverflow.fade, + softWrap: false, + maxLines: 1, + ); + + Future _tileOnTap() async => SonarrSeriesSeasonDetailsRouter.navigateTo( + context, + seriesId: widget.record.seriesId, + seasonNumber: widget.record.seasonNumber, + ); + + Future _tileOnLongPress() async => SonarrSeriesDetailsRouter.navigateTo( + context, + seriesId: widget.record.seriesId, + ); + + Future _trailingOnPressed() async { + Provider.of(context, listen: false).api.command.episodeSearch(episodeIds: [widget.record.id]) + .then((_) => LSSnackBar( + context: context, + title: 'Searching for Episode...', + message: widget.record.title, + type: SNACKBAR_TYPE.success, + )) + .catchError((error, stack) { + LunaLogger.error( + 'SonarrMissingTile', + '_trailingOnPressed', + 'Failed to search for episode: ${widget.record.id}', + error, + stack, + uploadToSentry: !(error is DioError), + ); + LSSnackBar( + context: context, + title: 'Failed to Search', + type: SNACKBAR_TYPE.failure, + ); + }); + } + + Future _trailingOnLongPress() async => SonarrReleasesRouter.navigateTo( + context, + episodeId: widget.record.id, + ); +} diff --git a/lib/modules/sonarr/modules/queue.dart b/lib/modules/sonarr/modules/queue.dart new file mode 100644 index 00000000..29f788da --- /dev/null +++ b/lib/modules/sonarr/modules/queue.dart @@ -0,0 +1,2 @@ +export 'queue/route.dart'; +export 'queue/widgets.dart'; diff --git a/lib/modules/sonarr/modules/queue/route.dart b/lib/modules/sonarr/modules/queue/route.dart new file mode 100644 index 00000000..d72e82a7 --- /dev/null +++ b/lib/modules/sonarr/modules/queue/route.dart @@ -0,0 +1,44 @@ +import 'package:fluro_fork/fluro_fork.dart'; +import 'package:flutter/material.dart' hide Router; +import 'package:lunasea/core.dart'; + +class SonarrQueueRouter { + static const String ROUTE_NAME = '/sonarr/queue/list'; + + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) { + router.define( + ROUTE_NAME, + handler: Handler(handlerFunc: (context, params) => _SonarrQueueRoute()), + transitionType: LunaRouter.transitionType, + ); + } +} + +class _SonarrQueueRoute extends StatefulWidget { + @override + State createState() => _State(); +} + +class _State extends State<_SonarrQueueRoute> { + final GlobalKey _scaffoldKey = GlobalKey(); + + @override + Widget build(BuildContext context) => Scaffold( + key: _scaffoldKey, + appBar: _appBar, + body: LSGenericMessage(text: 'Coming Soon!'), + ); + + Widget get _appBar => LunaAppBar( + context: context, + title: 'Queue', + popUntil: '/sonarr', + ); +} diff --git a/lib/modules/sonarr/modules/queue/widgets.dart b/lib/modules/sonarr/modules/queue/widgets.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/modules/sonarr/modules/releases.dart b/lib/modules/sonarr/modules/releases.dart new file mode 100644 index 00000000..7a79c8c8 --- /dev/null +++ b/lib/modules/sonarr/modules/releases.dart @@ -0,0 +1,2 @@ +export 'releases/route.dart'; +export 'releases/widgets.dart'; diff --git a/lib/modules/sonarr/modules/releases/route.dart b/lib/modules/sonarr/modules/releases/route.dart new file mode 100644 index 00000000..30bfead0 --- /dev/null +++ b/lib/modules/sonarr/modules/releases/route.dart @@ -0,0 +1,173 @@ +import 'package:fluro_fork/fluro_fork.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/material.dart' hide Router; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrReleasesRouter { + static const String ROUTE_NAME = '/sonarr/releases'; + + static Future navigateTo(BuildContext context, { + int episodeId, + int seriesId, + int seasonNumber, + }) async => LunaRouter.router.navigateTo( + context, + route(episodeId: episodeId, seriesId: seriesId, seasonNumber: seasonNumber), + ); + + static String route({ + int episodeId, + int seriesId, + int seasonNumber, + }) { + if(episodeId != null) return ROUTE_NAME+'/episode/$episodeId'; + if(seriesId != null && seasonNumber != null) return ROUTE_NAME+'/series/$seriesId/season/$seasonNumber'; + return SonarrHomeRouter.route(); + } + + static void defineRoutes(Router router) { + router.define( + ROUTE_NAME+'/episode/:episodeid', + handler: Handler(handlerFunc: (context, params) => _SonarrReleasesRoute( + episodeId: int.tryParse(params['episodeid'][0]) ?? -1, + seriesId: null, + seasonNumber: null, + )), + transitionType: LunaRouter.transitionType, + ); + router.define( + ROUTE_NAME+'/series/:seriesid/season/:seasonnumber', + handler: Handler(handlerFunc: (context, params) => _SonarrReleasesRoute( + episodeId: null, + seriesId: int.tryParse(params['seriesid'][0]) ?? -1, + seasonNumber: int.tryParse(params['seasonnumber'][0]) ?? -1, + )), + transitionType: LunaRouter.transitionType, + ); + } +} + +class _SonarrReleasesRoute extends StatefulWidget { + final int episodeId; + final int seriesId; + final int seasonNumber; + + _SonarrReleasesRoute({ + Key key, + this.episodeId, + this.seriesId, + this.seasonNumber, + }) : super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State<_SonarrReleasesRoute> { + final GlobalKey _scaffoldKey = GlobalKey(); + final GlobalKey _refreshKey = GlobalKey(); + final ScrollController _scrollController = ScrollController(); + Future> _future; + + @override + void initState() { + super.initState(); + SchedulerBinding.instance.scheduleFrameCallback((_) => _refresh()); + } + + Future _refresh() async { + if(context.read().api != null && mounted) setState(() { + if(widget.episodeId != null) { + _future = context.read().api.release.getReleases(episodeId: widget.episodeId); + } else if(widget.seriesId != null && widget.seasonNumber != null) { + _future = context.read().api.release.getSeasonReleases(seriesId: widget.seriesId, seasonNumber: widget.seasonNumber) + .then((data) => data = data.where((release) => release.fullSeason).toList()); + } else { + LunaLogger.warning( + '_SonarrReleasesRoute', + '_refresh', + 'No valid episodeId or (seriesId & seasonNumber) found', + ); + } + }); + } + + @override + Widget build(BuildContext context) => Scaffold( + key: _scaffoldKey, + appBar: _appBar, + body: _body, + ); + + Widget get _appBar => SonarrReleasesAppBar(context: context, scrollController: _scrollController); + + Widget get _body => LSRefreshIndicator( + refreshKey: _refreshKey, + onRefresh: _refresh, + child: FutureBuilder( + future: _future, + builder: (context, AsyncSnapshot> snapshot) { + if(snapshot.hasError) { + if(snapshot.connectionState != ConnectionState.waiting) { + LunaLogger.error( + '_SonarrReleasesRoute', + '_body', + 'Unable to fetch Sonarr releases: ${widget.episodeId}', + snapshot.error, + null, + uploadToSentry: !(snapshot.error is DioError), + ); + } + return LSErrorMessage(onTapHandler: () async => _refreshKey.currentState.show()); + } + if(snapshot.connectionState == ConnectionState.done && snapshot.hasData) return snapshot.data.length == 0 + ? _noReleases() + : _releases(snapshot.data); + return LSLoader(); + }, + ), + ); + + List _filterAndSort(List releases) { + if(releases == null || releases.length == 0) return releases; + List _filtered = new List.from(releases); + SonarrState _state = context.read(); + // Filter + _filtered = _filtered.where((release) { + if(_state.releasesSearchQuery != null && _state.releasesSearchQuery.isNotEmpty) + return release.title.toLowerCase().contains(_state.releasesSearchQuery.toLowerCase()); + return release != null; + }).toList(); + _filtered = _state.releasesHidingType.filter(_filtered); + // Sort + _filtered = _state.releasesSortType.sort(_filtered, _state.releasesSortAscending); + return _filtered; + } + + Widget _noReleases({ bool showButton = true }) => LSGenericMessage( + text: 'No Releases Found', + showButton: showButton, + buttonText: 'Refresh', + onTapHandler: _refresh, + ); + + Widget _releases(List releases) => Consumer( + builder: (context, state, _) { + List _filtered = _filterAndSort(releases); + return LSListView( + controller: _scrollController, + children: _filtered.length ==0 + ? [_noReleases(showButton: false)] + : List.generate( + _filtered.length, + (index) => SonarrReleasesReleaseTile( + key: ObjectKey(_filtered[index].guid), + release: _filtered[index], + isSeasonRelease: widget.episodeId == null, + ), + ), + ); + } + ); +} diff --git a/lib/modules/sonarr/modules/releases/widgets.dart b/lib/modules/sonarr/modules/releases/widgets.dart new file mode 100644 index 00000000..2374b22e --- /dev/null +++ b/lib/modules/sonarr/modules/releases/widgets.dart @@ -0,0 +1,4 @@ +export 'widgets/appbar.dart'; +export 'widgets/appbar_hide_button.dart'; +export 'widgets/appbar_sort_button.dart'; +export 'widgets/tile_release.dart'; diff --git a/lib/modules/sonarr/modules/releases/widgets/appbar.dart b/lib/modules/sonarr/modules/releases/widgets/appbar.dart new file mode 100644 index 00000000..e16a9844 --- /dev/null +++ b/lib/modules/sonarr/modules/releases/widgets/appbar.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +// ignore: non_constant_identifier_names +Widget SonarrReleasesAppBar({ + @required BuildContext context, + @required ScrollController scrollController, +}) => LunaAppBar( + context: context, + title: 'Releases', + bottom: _SearchBar(scrollController: scrollController), + popUntil: '/sonarr', +); + +class _SearchBar extends StatefulWidget implements PreferredSizeWidget { + final ScrollController scrollController; + + _SearchBar({ + Key key, + @required this.scrollController, + }) : super(key: key); + + @override + Size get preferredSize => Size.fromHeight(62.0); + + @override + State<_SearchBar> createState() => _State(scrollController: scrollController); +} + +class _State extends State<_SearchBar> { + final TextEditingController _controller = TextEditingController(); + final ScrollController scrollController; + + _State({ @required this.scrollController }); + + @override + void initState() { + super.initState(); + _controller.text = ''; + SchedulerBinding.instance.scheduleFrameCallback((_) { + Provider.of(context, listen: false).releasesSearchQuery = ''; + }); + } + + @override + Widget build(BuildContext context) => Consumer( + builder: (context, state, widget) => Row( + children: [ + Expanded( + child: LSTextInputBar( + controller: _controller, + autofocus: false, + onChanged: (text, updateController) => _onChange(text, updateController), + margin: EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 14.0), + ), + ), + SonarrReleasesAppBarHideButton(controller: scrollController), + SonarrReleasesAppBarSortButton(controller: scrollController), + ], + ), + ); + + void _onChange(String text, bool updateController) { + context.read().releasesSearchQuery = text; + if(updateController) _controller.text = text; + } +} diff --git a/lib/modules/sonarr/modules/releases/widgets/appbar_hide_button.dart b/lib/modules/sonarr/modules/releases/widgets/appbar_hide_button.dart new file mode 100644 index 00000000..8ecaab70 --- /dev/null +++ b/lib/modules/sonarr/modules/releases/widgets/appbar_hide_button.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrReleasesAppBarHideButton extends StatefulWidget { + final ScrollController controller; + + SonarrReleasesAppBarHideButton({ + Key key, + @required this.controller, + }): super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State { + @override + Widget build(BuildContext context) => LSCard( + child: Consumer( + builder: (context, state, widget) => PopupMenuButton( + shape: LunaSeaDatabaseValue.THEME_AMOLED.data && LunaSeaDatabaseValue.THEME_AMOLED_BORDER.data + ? LSRoundedShapeWithBorder() + : LSRoundedShape(), + icon: LSIcon(icon: Icons.visibility), + onSelected: (result) { + state.releasesHidingType = result; + _scrollBack(); + }, + itemBuilder: (context) => List>.generate( + SonarrReleasesHiding.values.length, + (index) => PopupMenuItem( + value: SonarrReleasesHiding.values[index], + child: Text( + SonarrReleasesHiding.values[index].readable, + style: TextStyle( + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + color: state.releasesHidingType == SonarrReleasesHiding.values[index] + ? LunaColours.accent + : Colors.white, + ), + ), + ), + ), + ), + ), + margin: EdgeInsets.fromLTRB(0.0, 0.0, 12.0, 14.0), + color: Theme.of(context).canvasColor, + ); + + void _scrollBack() { + if(widget.controller.hasClients) widget.controller.animateTo( + 1.00, + duration: Duration( + milliseconds: Constants.UI_NAVIGATION_SPEED*2, + ), + curve: Curves.easeOutSine, + ); + } +} \ No newline at end of file diff --git a/lib/modules/sonarr/widgets/releases_sorting_button.dart b/lib/modules/sonarr/modules/releases/widgets/appbar_sort_button.dart similarity index 67% rename from lib/modules/sonarr/widgets/releases_sorting_button.dart rename to lib/modules/sonarr/modules/releases/widgets/appbar_sort_button.dart index 8713e5e3..0fa28abc 100644 --- a/lib/modules/sonarr/widgets/releases_sorting_button.dart +++ b/lib/modules/sonarr/modules/releases/widgets/appbar_sort_button.dart @@ -2,33 +2,33 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/sonarr.dart'; -class SonarrReleasesSortButton extends StatefulWidget { +class SonarrReleasesAppBarSortButton extends StatefulWidget { final ScrollController controller; - SonarrReleasesSortButton({ + SonarrReleasesAppBarSortButton({ Key key, @required this.controller, }): super(key: key); @override - State createState() => _State(); + State createState() => _State(); } -class _State extends State { +class _State extends State { @override Widget build(BuildContext context) => LSCard( - child: Consumer( - builder: (context, model, widget) => PopupMenuButton( + child: Consumer( + builder: (context, state, widget) => PopupMenuButton( shape: LunaSeaDatabaseValue.THEME_AMOLED.data && LunaSeaDatabaseValue.THEME_AMOLED_BORDER.data ? LSRoundedShapeWithBorder() : LSRoundedShape(), icon: LSIcon(icon: Icons.sort), onSelected: (result) { - if(model.sortReleasesType == result) { - model.sortReleasesAscending = !model.sortReleasesAscending; + if(state.releasesSortType == result) { + state.releasesSortAscending = !state.releasesSortAscending; } else { - model.sortReleasesAscending = true; - model.sortReleasesType = result; + state.releasesSortAscending = true; + state.releasesSortType = result; } _scrollBack(); }, @@ -43,14 +43,17 @@ class _State extends State { SonarrReleasesSorting.values[index].readable, style: TextStyle( fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + color: state.releasesSortType == SonarrReleasesSorting.values[index] + ? LunaColours.accent + : Colors.white, ), ), - if(model.sortReleasesType == SonarrReleasesSorting.values[index]) Icon( - model.sortReleasesAscending + if(state.releasesSortType == SonarrReleasesSorting.values[index]) Icon( + state.releasesSortAscending ? Icons.arrow_upward : Icons.arrow_downward, size: Constants.UI_FONT_SIZE_SUBTITLE+2.0, - color: LSColors.accent, + color: LunaColours.accent, ), ], ), @@ -63,7 +66,7 @@ class _State extends State { ); void _scrollBack() { - widget.controller.animateTo( + if(widget.controller.hasClients) widget.controller.animateTo( 1.00, duration: Duration( milliseconds: Constants.UI_NAVIGATION_SPEED*2, @@ -71,4 +74,4 @@ class _State extends State { curve: Curves.easeOutSine, ); } -} +} \ No newline at end of file diff --git a/lib/modules/sonarr/widgets/search_result_tile.dart b/lib/modules/sonarr/modules/releases/widgets/tile_release.dart similarity index 51% rename from lib/modules/sonarr/widgets/search_result_tile.dart rename to lib/modules/sonarr/modules/releases/widgets/tile_release.dart index 041d20b8..3fea83ce 100644 --- a/lib/modules/sonarr/widgets/search_result_tile.dart +++ b/lib/modules/sonarr/modules/releases/widgets/tile_release.dart @@ -2,15 +2,17 @@ import 'package:expandable/expandable.dart'; import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/sonarr.dart'; -import 'package:lunasea/modules/home.dart'; -class SonarrSearchResultTile extends StatelessWidget { - final SonarrReleaseData data; +class SonarrReleasesReleaseTile extends StatelessWidget { final ExpandableController _controller = ExpandableController(); + final SonarrRelease release; + final bool isSeasonRelease; - SonarrSearchResultTile({ - @required this.data, - }); + SonarrReleasesReleaseTile({ + Key key, + @required this.release, + @required this.isSeasonRelease, + }): super(key: key); @override Widget build(BuildContext context) => LSExpandable( @@ -19,6 +21,53 @@ class SonarrSearchResultTile extends StatelessWidget { expanded: _expanded(context), ); + Widget _collapsed(BuildContext context) => LSCardTile( + title: LSTitle(text: release.title), + subtitle: RichText( + text: TextSpan( + style: TextStyle( + color: Colors.white70, + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + ), + children: [ + TextSpan( + style: TextStyle( + color: release.protocol == 'torrent' + ? LunaColours.purple + : LunaColours.blue, + fontWeight: FontWeight.bold, + ), + text: release.protocol.lsLanguage_Capitalize(), + ), + if(release.protocol == 'torrent') TextSpan( + text: ' (${release.seeders}/${release.leechers})', + style: TextStyle( + color: LunaColours.purple, + fontWeight: FontWeight.bold, + ), + ), + TextSpan(text: '\t•\t${release.indexer}\t•\t'), + TextSpan(text: '${release?.ageHours?.lsTime_releaseAgeString() ?? 'Unknown'}\n'), + TextSpan(text: '${release?.quality?.quality?.name ?? 'Unknown'}\t•\t'), + TextSpan(text: '${release?.size?.lsBytes_BytesToString() ?? 'Unknown'}'), + ] + ), + ), + trailing: LSIconButton( + icon: release.approved + ? Icons.file_download + : Icons.report, + color: release.approved + ? Colors.white + : LunaColours.red, + onPressed: () async => release.approved + ? _startDownload(context) + : _showWarnings(context), + ), + padContent: true, + onTap: () => _controller.toggle(), + ); + Widget _expanded(BuildContext context) => LSCard( child: InkWell( child: Row( @@ -28,43 +77,40 @@ class SonarrSearchResultTile extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LSTitle(text: data.title, softWrap: true, maxLines: 12), + LSTitle(text: release.title, softWrap: true, maxLines: 12), Padding( child: Wrap( direction: Axis.horizontal, runSpacing: 10.0, children: [ LSTextHighlighted( - text: data.protocol.lsLanguage_Capitalize(), - bgColor: data.isTorrent - ? LSColors.purple - : LSColors.blue, + text: release.protocol.lsLanguage_Capitalize(), + bgColor: release.protocol == 'torrent' + ? LunaColours.purple + : LunaColours.blue, + ), + LSTextHighlighted( + text: release?.indexer ?? 'Unknown', + bgColor: LunaColours.blueGrey, ), ], ), padding: EdgeInsets.only(top: 8.0, bottom: 2.0), ), Padding( - child: RichText( - text: TextSpan( - style: TextStyle( - color: Colors.white70, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + child: Column( + children: [ + _tableContent('age', release?.ageHours?.lsTime_releaseAgeString() ?? 'Unknown'), + _tableContent('quality', release?.quality?.quality?.name ?? 'Unknown'), + _tableContent('size', release?.size?.lsBytes_BytesToString() ?? 'Unknown'), + if(release.protocol == 'torrent') _tableContent( + 'statistics', + [ + '${release?.seeders?.toString() ?? 'Unknown'} Seeder${(release?.seeders ?? 0) != 1 ? 's' : ''}', + '${release?.leechers?.toString() ?? 'Unknown'} Leecher${(release?.leechers ?? 0) != 1 ? 's' : ''}', + ].join(' ${Constants.TEXT_BULLET} '), ), - children: [ - if(data.isTorrent) TextSpan( - text: '${data.seeders} Seeders\t•\t${data.leechers} Leechers\n', - style: TextStyle( - color: LSColors.purple, - fontWeight: FontWeight.bold, - ), - ), - TextSpan(text: '${data.quality ?? 'Unknown'}\t•\t'), - TextSpan(text: '${data.size.lsBytes_BytesToString() ?? 'Unknown'}\t•\t'), - TextSpan(text: '${data.indexer}\n'), - TextSpan(text: '${data.ageHours.lsTime_releaseAgeString() ?? 'Unknown'}'), - ], - ), + ], ), padding: EdgeInsets.only(top: 6.0, bottom: 10.0), ), @@ -76,15 +122,15 @@ class SonarrSearchResultTile extends StatelessWidget { child: LSButtonSlim( text: 'Download', onTap: () => _startDownload(context), - margin: data.approved + margin: release.approved ? EdgeInsets.zero : EdgeInsets.only(right: 6.0), ), ), - if(!data.approved) Expanded( + if(!release.approved) Expanded( child: LSButtonSlim( text: 'Rejected', - backgroundColor: LSColors.red, + backgroundColor: LunaColours.red, onTap: () => _showWarnings(context), margin: EdgeInsets.only(left: 6.0), ), @@ -105,82 +151,46 @@ class SonarrSearchResultTile extends StatelessWidget { ), ); - Widget _collapsed(BuildContext context) => LSCardTile( - title: LSTitle(text: data.title), - subtitle: RichText( - text: TextSpan( - style: TextStyle( - color: Colors.white70, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - ), - children: [ - TextSpan( - style: TextStyle( - color: data.isTorrent - ? LSColors.purple - : LSColors.blue, - fontWeight: FontWeight.bold, - ), - text: data.protocol.lsLanguage_Capitalize(), - ), - if(data.isTorrent) TextSpan( - text: ' (${data.seeders}/${data.leechers})', - style: TextStyle( - color: LSColors.purple, - fontWeight: FontWeight.bold, - ), - ), - TextSpan(text: '\t•\t${data.indexer}\t•\t'), - TextSpan(text: '${data.ageHours.lsTime_releaseAgeString() ?? 'Unknown'}\n'), - TextSpan(text: '${data.quality ?? 'Unknown'}\t•\t'), - TextSpan(text: '${data.size.lsBytes_BytesToString() ?? 'Unknown'}'), - ] - ), - ), - trailing: LSIconButton( - icon: data.approved - ? Icons.file_download - : Icons.report, - color: data.approved - ? Colors.white - : LSColors.red, - onPressed: () async => data.approved - ? _startDownload(context) - : _showWarnings(context), - ), - padContent: true, - onTap: () => _controller.toggle(), + Widget _tableContent(String title, String body) => LSTableContent( + title: title, + body: body, + padding: EdgeInsets.symmetric(horizontal: 0.0, vertical: 2.0), ); - Future _showWarnings(BuildContext context) async { - String reject = ''; - for(var i=0; i _startDownload(BuildContext context) async { - SonarrAPI _api = SonarrAPI.from(Database.currentProfileObject); - await _api.downloadRelease(data.guid, data.indexerId) + if(context.read().api != null) context.read().api.release.addRelease( + guid: release.guid, + indexerId: release.indexerId, + useVersion3: isSeasonRelease, + ) .then((_) => LSSnackBar( context: context, - title: 'Downloading...', - message: data.title, + title: 'Downloading Release...', + message: release.title, type: SNACKBAR_TYPE.success, - showButton: true, - buttonText: 'Back', - buttonOnPressed: () => Navigator.of(context).popUntil((Route route) { - return !route.willHandlePopInternally - && route is ModalRoute - && (route.settings.name == Sonarr.ROUTE_NAME || route.settings.name == Home.ROUTE_NAME); - }), - )) - .catchError((_) => LSSnackBar( - context: context, - title: 'Failed to Start Downloading', - message: Constants.CHECK_LOGS_MESSAGE, - type: SNACKBAR_TYPE.failure, - )); + )) + .catchError((error, stack) { + LunaLogger.error( + 'SonarrReleasesReleaseTile', + '_startDownload', + 'Unable to download release: ${release.guid}', + error, + stack, + uploadToSentry: !(error is DioError), + ); + LSSnackBar( + context: context, + title: 'Failed to Download Release', + type: SNACKBAR_TYPE.failure, + ); + }); } -} + + Future _showWarnings(BuildContext context) async { + String rejections = ''; + for(var i=0; i createState() => _State(); +} + +class _State extends State with AutomaticKeepAliveClientMixin { + final GlobalKey _scaffoldKey = GlobalKey(); + final GlobalKey _refreshKey = GlobalKey(); + + @override + bool get wantKeepAlive => true; + + @override + void initState() { + super.initState(); + SchedulerBinding.instance.scheduleFrameCallback((_) => _refresh()); + } + + Future _refresh() async { + SonarrState _state = Provider.of(context, listen: false); + _state.resetSeries(); + _state.resetQualityProfiles(); + _state.resetLanguageProfiles(); + _state.resetTags(); + await Future.wait([ + _state.series, + _state.qualityProfiles, + _state.tags, + if(_state.enableVersion3) _state.languageProfiles, + ]); + } + + @override + Widget build(BuildContext context) { + super.build(context); + return Scaffold( + key: _scaffoldKey, + body: _body, + appBar: _appBar, + ); + } + + Widget get _appBar => LunaAppBar.empty( + child: SonarrSeriesSearchBar(scrollController: widget.scrollController), + height: 62.0, + ); + + Widget get _body => LSRefreshIndicator( + refreshKey: _refreshKey, + onRefresh: _refresh, + child: Selector>, + Future>, + Future>, + bool + >>( + selector: (_, state) => Tuple4( + state.series, + state.qualityProfiles, + state.languageProfiles, + state.enableVersion3, + ), + builder: (context, tuple, _) => FutureBuilder( + future: Future.wait([ + tuple.item1, + tuple.item2, + if(tuple.item4) tuple.item3, + ]), + builder: (context, AsyncSnapshot> snapshot) { + if(snapshot.hasError) { + if(snapshot.connectionState != ConnectionState.waiting) { + LunaLogger.error( + 'SonarrSeriesRoute', + '_body', + 'Unable to fetch Sonarr series', + snapshot.error, + null, + uploadToSentry: !(snapshot.error is DioError), + ); + } + return LSErrorMessage(onTapHandler: () async => _refreshKey.currentState.show()); + } + if(snapshot.hasData) return snapshot.data.length == 0 + ? _noSeries() + : snapshot.data.length > 2 + ? _series(snapshot.data[0], snapshot.data[1], snapshot.data[2]) + : _series(snapshot.data[0], snapshot.data[1], null); + return LSLoader(); + }, + ), + ), + ); + + List _filterAndSort(List series, List profiles, String query) { + if(series == null || series.length == 0) return series; + SonarrState _state = Provider.of(context, listen: false); + List _filtered = new List.from(series); + // Filter + _filtered = _filtered.where((show) { + if(query != null && query.isNotEmpty) return show.title.toLowerCase().contains(query.toLowerCase()); + return show != null; + }).toList(); + _filtered = _state.seriesHidingType.filter(_filtered); + // Sort + _filtered = _state.seriesSortType.sort(_filtered, _state.seriesSortAscending); + return _filtered; + } + + Widget _series( + List series, + List qualities, + List languages, + ) => Selector( + selector: (_, state) => state.seriesSearchQuery, + builder: (context, query, _) { + List _filtered = _filterAndSort(series, qualities, query); + return LSListView( + controller: widget.scrollController, + children: _filtered.length == 0 + ? [_noSeries(showButton: false)] + : List.generate( + _filtered.length, + (index) => SonarrSeriesTile( + series: _filtered[index], + profile: qualities.firstWhere((element) => element.id == _filtered[index].profileId, orElse: null), + ), + ), + ); + }, + ); + + Widget _noSeries({ bool showButton = true }) => LSGenericMessage( + text: 'No Series Found', + showButton: showButton, + buttonText: 'Refresh', + onTapHandler: () async => _refreshKey.currentState.show(), + ); +} diff --git a/lib/modules/sonarr/modules/series/widgets.dart b/lib/modules/sonarr/modules/series/widgets.dart new file mode 100644 index 00000000..f37dad01 --- /dev/null +++ b/lib/modules/sonarr/modules/series/widgets.dart @@ -0,0 +1,4 @@ +export 'widgets/search_bar.dart'; +export 'widgets/series_tile.dart'; +export 'widgets/search_bar_hide_button.dart'; +export 'widgets/search_bar_sort_button.dart'; diff --git a/lib/modules/sonarr/modules/series/widgets/search_bar.dart b/lib/modules/sonarr/modules/series/widgets/search_bar.dart new file mode 100644 index 00000000..a17e5d76 --- /dev/null +++ b/lib/modules/sonarr/modules/series/widgets/search_bar.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesSearchBar extends StatefulWidget implements PreferredSizeWidget { + final ScrollController scrollController; + + SonarrSeriesSearchBar({ + Key key, + @required this.scrollController, + }) : super(key: key); + + @override + Size get preferredSize => Size.fromHeight(62.0); + + @override + State createState() => _State(); +} + +class _State extends State { + final TextEditingController _controller = TextEditingController(); + + @override + void initState() { + super.initState(); + _controller.text = context.read().seriesSearchQuery; + } + + @override + Widget build(BuildContext context) => Padding( + child: Row( + children: [ + Expanded( + child: Consumer( + builder: (context, state, _) => LSTextInputBar( + controller: _controller, + autofocus: false, + onChanged: (text, updateController) => _onChange(text, updateController), + margin: EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 14.0), + ), + ), + ), + SonarrSeriesSearchBarHideButton(controller: widget.scrollController), + SonarrSeriesSearchBarSortButton(controller: widget.scrollController), + ], + ), + padding: EdgeInsets.only(top: 1.0, bottom: 1.0), + ); + + void _onChange(String text, bool updateController) { + context.read().seriesSearchQuery = text; + if(updateController) _controller.text = text; + } +} diff --git a/lib/modules/sonarr/modules/series/widgets/search_bar_hide_button.dart b/lib/modules/sonarr/modules/series/widgets/search_bar_hide_button.dart new file mode 100644 index 00000000..11eec127 --- /dev/null +++ b/lib/modules/sonarr/modules/series/widgets/search_bar_hide_button.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesSearchBarHideButton extends StatefulWidget { + final ScrollController controller; + + SonarrSeriesSearchBarHideButton({ + Key key, + @required this.controller, + }): super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State { + @override + Widget build(BuildContext context) => LSCard( + child: Consumer( + builder: (context, state, widget) => PopupMenuButton( + shape: LunaSeaDatabaseValue.THEME_AMOLED.data && LunaSeaDatabaseValue.THEME_AMOLED_BORDER.data + ? LSRoundedShapeWithBorder() + : LSRoundedShape(), + icon: LSIcon(icon: Icons.visibility), + onSelected: (result) { + state.seriesHidingType = result; + _scrollBack(); + }, + itemBuilder: (context) => List>.generate( + SonarrSeriesHiding.values.length, + (index) => PopupMenuItem( + value: SonarrSeriesHiding.values[index], + child: Text( + SonarrSeriesHiding.values[index].readable, + style: TextStyle( + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + color: state.seriesHidingType == SonarrSeriesHiding.values[index] + ? LunaColours.accent + : Colors.white, + ), + ), + ), + ), + ), + ), + margin: EdgeInsets.fromLTRB(0.0, 0.0, 12.0, 14.0), + color: Theme.of(context).canvasColor, + ); + + void _scrollBack() { + if(widget.controller.hasClients) widget.controller.animateTo( + 1.00, + duration: Duration( + milliseconds: Constants.UI_NAVIGATION_SPEED*2, + ), + curve: Curves.easeOutSine, + ); + } +} \ No newline at end of file diff --git a/lib/modules/sonarr/widgets/catalogue_sorting_button.dart b/lib/modules/sonarr/modules/series/widgets/search_bar_sort_button.dart similarity index 56% rename from lib/modules/sonarr/widgets/catalogue_sorting_button.dart rename to lib/modules/sonarr/modules/series/widgets/search_bar_sort_button.dart index ae0fae58..a7c9185a 100644 --- a/lib/modules/sonarr/widgets/catalogue_sorting_button.dart +++ b/lib/modules/sonarr/modules/series/widgets/search_bar_sort_button.dart @@ -2,55 +2,58 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/sonarr.dart'; -class SonarrCatalogueSortButton extends StatefulWidget { +class SonarrSeriesSearchBarSortButton extends StatefulWidget { final ScrollController controller; - SonarrCatalogueSortButton({ + SonarrSeriesSearchBarSortButton({ Key key, @required this.controller, }): super(key: key); @override - State createState() => _State(); + State createState() => _State(); } -class _State extends State { +class _State extends State { @override Widget build(BuildContext context) => LSCard( - child: Consumer( - builder: (context, model, widget) => PopupMenuButton( + child: Consumer( + builder: (context, state, widget) => PopupMenuButton( shape: LunaSeaDatabaseValue.THEME_AMOLED.data && LunaSeaDatabaseValue.THEME_AMOLED_BORDER.data ? LSRoundedShapeWithBorder() : LSRoundedShape(), icon: LSIcon(icon: Icons.sort), onSelected: (result) { - if(model.sortCatalogueType == result) { - model.sortCatalogueAscending = !model.sortCatalogueAscending; + if(state.seriesSortType == result) { + state.seriesSortAscending = !state.seriesSortAscending; } else { - model.sortCatalogueAscending = true; - model.sortCatalogueType = result; + state.seriesSortAscending = true; + state.seriesSortType = result; } _scrollBack(); }, - itemBuilder: (context) => List>.generate( - SonarrCatalogueSorting.values.length, - (index) => PopupMenuItem( - value: SonarrCatalogueSorting.values[index], + itemBuilder: (context) => List>.generate( + SonarrSeriesSorting.values.length, + (index) => PopupMenuItem( + value: SonarrSeriesSorting.values[index], child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - SonarrCatalogueSorting.values[index].readable, + SonarrSeriesSorting.values[index].readable, style: TextStyle( fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + color: state.seriesSortType == SonarrSeriesSorting.values[index] + ? LunaColours.accent + : Colors.white, ), ), - if(model.sortCatalogueType == SonarrCatalogueSorting.values[index]) Icon( - model.sortCatalogueAscending + if(state.seriesSortType == SonarrSeriesSorting.values[index]) Icon( + state.seriesSortAscending ? Icons.arrow_upward : Icons.arrow_downward, size: Constants.UI_FONT_SIZE_SUBTITLE+2.0, - color: LSColors.accent, + color: LunaColours.accent, ), ], ), @@ -63,7 +66,7 @@ class _State extends State { ); void _scrollBack() { - widget.controller.animateTo( + if(widget.controller.hasClients) widget.controller.animateTo( 1.00, duration: Duration( milliseconds: Constants.UI_NAVIGATION_SPEED*2, @@ -71,4 +74,4 @@ class _State extends State { curve: Curves.easeOutSine, ); } -} +} \ No newline at end of file diff --git a/lib/modules/sonarr/modules/series/widgets/series_tile.dart b/lib/modules/sonarr/modules/series/widgets/series_tile.dart new file mode 100644 index 00000000..acfb0bf8 --- /dev/null +++ b/lib/modules/sonarr/modules/series/widgets/series_tile.dart @@ -0,0 +1,222 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesTile extends StatefulWidget { + final SonarrSeries series; + final SonarrQualityProfile profile; + + SonarrSeriesTile({ + Key key, + @required this.series, + @required this.profile, + }) : super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State { + final double _height = 90.0; + final double _width = 60.0; + final double _padding = 8.0; + + @override + Widget build(BuildContext context) => Selector>>( + selector: (_, state) => state.series, + builder: (context, series, _) => LSCard( + child: InkWell( + child: Row( + children: [ + _poster, + Expanded(child: _information), + ], + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + ), + borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), + onTap: () async => _tileOnTap(), + onLongPress: () async => SonarrAppBarSeriesSettingsAction.handler(context, widget.series), + ), + decoration: LSCardBackground( + uri: Provider.of(context, listen: false).getBannerURL(widget.series.id), + headers: Provider.of(context, listen: false).headers, + ), + ), + ); + + Widget get _poster => LSNetworkImage( + url: Provider.of(context, listen: false).getPosterURL(widget.series.id), + placeholder: 'assets/images/sonarr/noseriesposter.png', + height: _height, + width: _width, + headers: Provider.of(context, listen: false).headers.cast(), + ); + + Widget get _information => Padding( + child: Container( + child: Column( + children: [ + LSTitle(text: widget.series.title, darken: !widget.series.monitored, maxLines: 1), + _subtitleOne, + _subtitleTwo, + _subtitleThree, + ], + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + ), + height: (_height-(_padding*2)), + ), + padding: EdgeInsets.all(_padding), + ); + + Widget get _subtitleOne => RichText( + text: TextSpan( + style: TextStyle( + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + color: widget.series.monitored ? Colors.white70 : Colors.white30, + ), + children: [ + TextSpan( + text: widget.series.lunaEpisodeCount, + style: TextStyle( + color: Provider.of(context).seriesSortType == SonarrSeriesSorting.EPISODES + ? widget.series.monitored + ? LunaColours.accent + : LunaColours.accent.withOpacity(0.30) + : null, + fontWeight: Provider.of(context).seriesSortType == SonarrSeriesSorting.EPISODES + ? FontWeight.w600 + : null, + ), + ), + TextSpan(text: ' ${Constants.TEXT_BULLET} '), + TextSpan(text: widget.series.lunaSeasonCount), + TextSpan(text: ' ${Constants.TEXT_BULLET} '), + TextSpan( + text: widget.series.lunaSizeOnDisk, + style: TextStyle( + color: Provider.of(context).seriesSortType == SonarrSeriesSorting.SIZE + ? widget.series.monitored + ? LunaColours.accent + : LunaColours.accent.withOpacity(0.30) + : null, + fontWeight: Provider.of(context).seriesSortType == SonarrSeriesSorting.SIZE + ? FontWeight.w600 + : null, + ), + ), + ], + ), + overflow: TextOverflow.fade, + softWrap: false, + maxLines: 1, + ); + + Widget get _subtitleTwo => RichText( + text: TextSpan( + style: TextStyle( + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + color: widget.series.monitored ? Colors.white70 : Colors.white30, + ), + children: [ + TextSpan( + text: widget.series.lunaSeriesType, + style: TextStyle( + color: Provider.of(context).seriesSortType == SonarrSeriesSorting.TYPE + ? widget.series.monitored + ? LunaColours.accent + : LunaColours.accent.withOpacity(0.30) + : null, + fontWeight: Provider.of(context).seriesSortType == SonarrSeriesSorting.TYPE + ? FontWeight.w600 + : null, + ), + ), + TextSpan(text: ' ${Constants.TEXT_BULLET} '), + TextSpan( + text: widget.profile?.name ?? 'Unknown', + style: TextStyle( + color: Provider.of(context).seriesSortType == SonarrSeriesSorting.QUALITY + ? widget.series.monitored + ? LunaColours.accent + : LunaColours.accent.withOpacity(0.30) + : null, + fontWeight: Provider.of(context).seriesSortType == SonarrSeriesSorting.QUALITY + ? FontWeight.w600 + : null, + ), + ), + ], + ), + overflow: TextOverflow.fade, + softWrap: false, + maxLines: 1, + ); + + Widget get _subtitleThree => RichText( + text: TextSpan( + style: TextStyle( + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + color: widget.series.monitored ? Colors.white70 : Colors.white30, + ), + children: [ + TextSpan( + text: widget.series.lunaAirsOn, + style: TextStyle( + color: + Provider.of(context).seriesSortType == SonarrSeriesSorting.NETWORK || + Provider.of(context).seriesSortType == SonarrSeriesSorting.NEXT_AIRING + ? widget.series.monitored + ? LunaColours.accent + : LunaColours.accent.withOpacity(0.30) + : null, + fontWeight: + Provider.of(context).seriesSortType == SonarrSeriesSorting.NETWORK || + Provider.of(context).seriesSortType == SonarrSeriesSorting.NEXT_AIRING + ? FontWeight.w600 + : null, + ), + ), + TextSpan( + text: ' ${Constants.TEXT_BULLET} ', + style: TextStyle( + color: Provider.of(context).seriesSortType == SonarrSeriesSorting.NEXT_AIRING + ? widget.series.monitored + ? LunaColours.accent + : LunaColours.accent.withOpacity(0.30) + : null, + ), + ), + if(Provider.of(context).seriesSortType != SonarrSeriesSorting.NEXT_AIRING) TextSpan( + text: widget.series.lunaDateAdded, + style: TextStyle( + color: Provider.of(context).seriesSortType == SonarrSeriesSorting.DATE_ADDED + ? widget.series.monitored + ? LunaColours.accent + : LunaColours.accent.withOpacity(0.30) + : null, + fontWeight: Provider.of(context).seriesSortType == SonarrSeriesSorting.DATE_ADDED + ? FontWeight.w600 + : null, + ), + ), + if(Provider.of(context).seriesSortType == SonarrSeriesSorting.NEXT_AIRING) TextSpan( + text: widget.series.lunaNextAiring, + style: TextStyle( + color: widget.series.monitored + ? LunaColours.accent + : LunaColours.accent.withOpacity(0.30), + fontWeight: FontWeight.w600, + ), + ), + ], + ), + overflow: TextOverflow.fade, + softWrap: false, + maxLines: 1, + ); + + Future _tileOnTap() async => SonarrSeriesDetailsRouter.navigateTo(context, seriesId: widget.series.id); +} diff --git a/lib/modules/sonarr/modules/series_add.dart b/lib/modules/sonarr/modules/series_add.dart new file mode 100644 index 00000000..680999f3 --- /dev/null +++ b/lib/modules/sonarr/modules/series_add.dart @@ -0,0 +1,2 @@ +export 'series_add/route.dart'; +export 'series_add/widgets.dart'; diff --git a/lib/modules/sonarr/modules/series_add/route.dart b/lib/modules/sonarr/modules/series_add/route.dart new file mode 100644 index 00000000..8a392b00 --- /dev/null +++ b/lib/modules/sonarr/modules/series_add/route.dart @@ -0,0 +1,53 @@ +import 'package:fluro_fork/fluro_fork.dart'; +import 'package:flutter/material.dart' hide Router; +import 'package:flutter/scheduler.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesAddRouter { + static const String ROUTE_NAME = '/sonarr/series/add'; + + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) { + router.define( + ROUTE_NAME, + handler: Handler(handlerFunc: (context, params) => _SonarrSeriesAddRoute()), + transitionType: LunaRouter.transitionType, + ); + } +} + +class _SonarrSeriesAddRoute extends StatefulWidget { + @override + State createState() => _State(); +} + +class _State extends State<_SonarrSeriesAddRoute> { + final GlobalKey _scaffoldKey = GlobalKey(); + + @override + void initState() { + super.initState(); + SchedulerBinding.instance.scheduleFrameCallback((_) => _refresh()); + } + + Future _refresh() async { + context.read().fetchRootFolders(); + context.read().resetQualityProfiles(); + context.read().resetLanguageProfiles(); + context.read().resetTags(); + } + + @override + Widget build(BuildContext context) => Scaffold( + key: _scaffoldKey, + appBar: SonarrSeriesAddAppBar(context: context), + body: SonarrSeriesAddSearchResults(), + ); +} diff --git a/lib/modules/sonarr/modules/series_add/widgets.dart b/lib/modules/sonarr/modules/series_add/widgets.dart new file mode 100644 index 00000000..14fc4c2d --- /dev/null +++ b/lib/modules/sonarr/modules/series_add/widgets.dart @@ -0,0 +1,3 @@ +export 'widgets/appbar.dart'; +export 'widgets/search_results.dart'; +export 'widgets/search_results_tile.dart'; diff --git a/lib/modules/sonarr/modules/series_add/widgets/appbar.dart b/lib/modules/sonarr/modules/series_add/widgets/appbar.dart new file mode 100644 index 00000000..754f6356 --- /dev/null +++ b/lib/modules/sonarr/modules/series_add/widgets/appbar.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +// ignore: non_constant_identifier_names +Widget SonarrSeriesAddAppBar({ + @required BuildContext context, +}) => LunaAppBar( + context: context, + title: 'Add Series', + bottom: _SearchBar(), + popUntil: '/sonarr', +); + +class _SearchBar extends StatefulWidget implements PreferredSizeWidget { + @override + Size get preferredSize => Size.fromHeight(62.0); + + @override + State<_SearchBar> createState() => _State(); +} + +class _State extends State<_SearchBar> { + final TextEditingController _controller = TextEditingController(); + + @override + void initState() { + super.initState(); + _controller.text = context.read().addSearchQuery; + } + + @override + Widget build(BuildContext context) => Consumer( + builder: (context, state, widget) => Row( + children: [ + Expanded( + child: LSTextInputBar( + controller: _controller, + autofocus: _controller.text.isEmpty, + onChanged: (text, updateController) => _onChange(text, updateController), + onSubmitted: _onSubmit, + margin: EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 14.0), + ), + ), + ], + ), + ); + + void _onChange(String text, bool updateController) { + context.read().addSearchQuery = text; + if(updateController) _controller.text = text; + } + + Future _onSubmit(String value) async { + if(value.isNotEmpty) context.read().fetchSeriesLookup(); + } +} diff --git a/lib/modules/sonarr/modules/series_add/widgets/search_results.dart b/lib/modules/sonarr/modules/series_add/widgets/search_results.dart new file mode 100644 index 00000000..35423378 --- /dev/null +++ b/lib/modules/sonarr/modules/series_add/widgets/search_results.dart @@ -0,0 +1,69 @@ + +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesAddSearchResults extends StatefulWidget { + @override + State createState() => _State(); +} + +class _State extends State { + final GlobalKey _refreshKey = GlobalKey(); + + Future _refresh() async { + if(context.read().addSearchQuery.isNotEmpty) context.read().fetchSeriesLookup(); + } + + @override + Widget build(BuildContext context) => Selector>>( + selector: (_, state) => state.seriesLookup, + builder: (context, future, _) { + if(future == null) return Container(); + return _futureBuilder(context, future); + }, + ); + + Widget _futureBuilder(BuildContext context, Future> future) => LSRefreshIndicator( + refreshKey: _refreshKey, + onRefresh: _refresh, + child: FutureBuilder( + future: Future.wait([ + future, + context.watch().series, + ]), + builder: (context, AsyncSnapshot snapshot) { + if(snapshot.hasError) { + if(snapshot.connectionState != ConnectionState.waiting) { + LunaLogger.error( + 'SonarrSeriesAddSearchResults', + '_futureBuilder', + 'Unable to fetch Sonarr series lookup', + snapshot.error, + null, + uploadToSentry: !(snapshot.error is DioError), + ); + } + return LSErrorMessage(onTapHandler: () async => _refreshKey.currentState.show()); + } + if(snapshot.connectionState == ConnectionState.none) + return Container(); + if(snapshot.connectionState == ConnectionState.done && snapshot.hasData) + return _results(snapshot.data[0], snapshot.data[1]); + return LSLoader(); + }, + ), + ); + + Widget _results(List results, List series) => LSListView( + children: results.length == 0 + ? [ LSGenericMessage(text: 'No Results Found') ] + : List.generate( + results.length, + (index) => SonarrSeriesAddSearchResultTile( + series: results[index], + exists: series.indexWhere((series) => series.tvdbId == results[index].tvdbId) >= 0, + ), + ), + ); +} diff --git a/lib/modules/sonarr/modules/series_add/widgets/search_results_tile.dart b/lib/modules/sonarr/modules/series_add/widgets/search_results_tile.dart new file mode 100644 index 00000000..5c56ab51 --- /dev/null +++ b/lib/modules/sonarr/modules/series_add/widgets/search_results_tile.dart @@ -0,0 +1,128 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesAddSearchResultTile extends StatefulWidget { + final SonarrSeriesLookup series; + final bool onTapShowOverview; + final bool exists; + + SonarrSeriesAddSearchResultTile({ + Key key, + @required this.series, + @required this.exists, + this.onTapShowOverview = false, + }) : super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State { + final double _height = 90.0; + final double _width = 60.0; + final double _padding = 8.0; + + @override + Widget build(BuildContext context) => LSCard( + child: InkWell( + child: Row( + children: [ + _poster(context), + Expanded(child: _information), + ], + ), + onTap: () async => _onTap(context), + borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), + ), + decoration: widget.series.lunaBannerURL == null ? null : LSCardBackground( + uri: widget.series.lunaBannerURL, + headers: Provider.of(context, listen: false).headers, + ), + ); + + Widget _poster(BuildContext context) { + if(widget.series.remotePoster != null) return LSNetworkImage( + url: widget.series.remotePoster, + placeholder: 'assets/images/sonarr/noseriesposter.png', + height: _height, + width: _width, + headers: Provider.of(context, listen: false).headers.cast(), + ); + return ClipRRect( + child: Image.asset( + 'assets/images/sonarr/noseriesposter.png', + width: _width, + height: _height, + ), + borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), + ); + } + + Widget get _information => Padding( + child: Container( + child: Column( + children: [ + LSTitle(text: widget.series.title, darken: widget.exists, maxLines: 1), + _subtitleOne, + _subtitleTwo, + ], + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + ), + height: (_height-(_padding*2)), + ), + padding: EdgeInsets.all(_padding), + ); + + Widget get _subtitleOne => RichText( + text: TextSpan( + style: TextStyle( + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + color: widget.exists ? Colors.white30 : Colors.white70, + ), + children: [ + TextSpan(text: widget.series.seasonCount == 1 ? '1 Season' : '${widget.series.seasonCount} Seasons'), + TextSpan(text: ' ${Constants.TEXT_BULLET} '), + TextSpan(text: (widget.series.year ?? 0) == 0 ? 'Unknown Year' : widget.series.year.toString()), + TextSpan(text: ' ${Constants.TEXT_BULLET} '), + TextSpan(text: widget.series.network ?? 'Unknown Network'), + ], + ), + overflow: TextOverflow.fade, + softWrap: false, + maxLines: 1, + ); + + Widget get _subtitleTwo => RichText( + text: TextSpan( + style: TextStyle( + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + fontStyle: FontStyle.italic, + color: widget.exists ? Colors.white30 : Colors.white70, + ), + text: '${widget.series.overview ?? 'No summary is available.'}\n', + ), + overflow: TextOverflow.fade, + softWrap: true, + maxLines: 2, + ); + + Future _onTap(BuildContext context) async { + if(widget.onTapShowOverview) { + LunaDialogs.textPreview(context, widget.series.title, widget.series.overview ?? 'No summary is available.'); + } else if(widget.exists) { + Provider.of(context, listen: false).enableVersion3 + ? SonarrSeriesDetailsRouter.navigateTo(context, seriesId: widget.series.id ?? -1) + : LSSnackBar( + context: context, + title: 'Series Already Exists', + message: 'This series already exists in Sonarr', + type: SNACKBAR_TYPE.info, + ); + } else { + SonarrSeriesAddDetailsRouter.navigateTo(context, tvdbId: widget.series.tvdbId ?? -1); + } + } +} diff --git a/lib/modules/sonarr/modules/series_add_details.dart b/lib/modules/sonarr/modules/series_add_details.dart new file mode 100644 index 00000000..8558e3ad --- /dev/null +++ b/lib/modules/sonarr/modules/series_add_details.dart @@ -0,0 +1,2 @@ +export 'series_add_details/route.dart'; +export 'series_add_details/widgets.dart'; diff --git a/lib/modules/sonarr/modules/series_add_details/route.dart b/lib/modules/sonarr/modules/series_add_details/route.dart new file mode 100644 index 00000000..c313f018 --- /dev/null +++ b/lib/modules/sonarr/modules/series_add_details/route.dart @@ -0,0 +1,198 @@ +import 'package:fluro_fork/fluro_fork.dart'; +import 'package:flutter/material.dart' hide Router; +import 'package:flutter/scheduler.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesAddDetailsRouter { + static const String ROUTE_NAME = '/sonarr/series/add/details/:tvdbid'; + + static Future navigateTo(BuildContext context, { + @required int tvdbId, + }) async => LunaRouter.router.navigateTo( + context, + route(tvdbId: tvdbId), + ); + + static String route({ @required int tvdbId }) => ROUTE_NAME + .replaceFirst(':tvdbid', tvdbId?.toString() ?? '-1'); + + static void defineRoutes(Router router) { + router.define( + ROUTE_NAME, + handler: Handler(handlerFunc: (context, params) => _SonarrSeriesAddDetailsRoute( + tvdbId: int.tryParse(params['tvdbid'][0]) ?? -1, + )), + transitionType: LunaRouter.transitionType, + ); + } +} + +class _SonarrSeriesAddDetailsRoute extends StatefulWidget { + final int tvdbId; + + _SonarrSeriesAddDetailsRoute({ + Key key, + @required this.tvdbId, + }): super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State<_SonarrSeriesAddDetailsRoute> { + final GlobalKey _scaffoldKey = GlobalKey(); + final GlobalKey _refreshKey = GlobalKey(); + bool _loaded = false; + + @override + void initState() { + super.initState(); + SchedulerBinding.instance.scheduleFrameCallback((_) => _setDefaults()); + } + + Future _setDefaults() async { + // Get the state, wait for the futures + SonarrState _state = Provider.of(context, listen: false); + await Future.wait([ + _state.rootFolders, + _state.seriesLookup, + _state.qualityProfiles, + if(_state.enableVersion3) _state.languageProfiles, + ]); + SonarrSeriesLookup series = (await _state.seriesLookup).firstWhere( + (series) => series?.tvdbId == widget.tvdbId, + orElse: () => null, + ); + if(series != null) { + series.monitored = SonarrDatabaseValue.ADD_SERIES_DEFAULT_MONITORED.data; + series.seasonFolder = SonarrDatabaseValue.ADD_SERIES_DEFAULT_USE_SEASON_FOLDERS.data; + await _setDefaultRootFolder(series); + await _setDefaultQualityProfile(series); + await _setDefaultMonitorStatus(series); + if(_state.enableVersion3) await _setDefaultLanguageProfile(series); + } + // Set the defaults + setState(() => _loaded = true); + } + + Future _setDefaultMonitorStatus(SonarrSeriesLookup series) async { + SonarrMonitorStatus _status = SonarrDatabaseValue.ADD_SERIES_DEFAULT_MONITOR_STATUS.data; + _status.process(series.seasons); + } + + Future _setDefaultRootFolder(SonarrSeriesLookup series) async { + List _rootFolders = await Provider.of(context, listen: false).rootFolders; + SonarrRootFolder _rootFolder = _rootFolders.firstWhere( + (element) => element.id == SonarrDatabaseValue.ADD_SERIES_DEFAULT_ROOT_FOLDER.data, + orElse: () => null, + ); + series.rootFolderPath = (_rootFolder?.path ?? _rootFolders[0].path); + SonarrDatabaseValue.ADD_SERIES_DEFAULT_ROOT_FOLDER.put((_rootFolder?.id ?? _rootFolders[0].id)); + } + + Future _setDefaultQualityProfile(SonarrSeriesLookup series) async { + List _profiles = await Provider.of(context, listen: false).qualityProfiles; + SonarrQualityProfile _profile = _profiles.firstWhere( + (element) => element.id == SonarrDatabaseValue.ADD_SERIES_DEFAULT_QUALITY_PROFILE.data, + orElse: () => null, + ); + series.qualityProfileId = (_profile?.id ?? _profiles[0].id); + series.profileId = (_profile?.id ?? _profiles[0].id); + SonarrDatabaseValue.ADD_SERIES_DEFAULT_QUALITY_PROFILE.put((_profile?.id ?? _profiles[0].id)); + } + + Future _setDefaultLanguageProfile(SonarrSeriesLookup series) async { + List _profiles = await Provider.of(context, listen: false).languageProfiles; + SonarrLanguageProfile _profile = _profiles.firstWhere( + (element) => element.id == SonarrDatabaseValue.ADD_SERIES_DEFAULT_LANGUAGE_PROFILE.data, + orElse: () => null, + ); + series.languageProfileId = (_profile?.id ?? _profiles[0].id); + SonarrDatabaseValue.ADD_SERIES_DEFAULT_LANGUAGE_PROFILE.put((_profile?.id ?? _profiles[0].id)); + } + + Future _refresh() async { + // Refresh the necessary data + context.read().fetchRootFolders(); + context.read().resetQualityProfiles(); + context.read().resetLanguageProfiles(); + context.read().resetTags(); + // Wait for the data to load + await Future.wait([ + context.read().rootFolders, + context.read().qualityProfiles, + if(context.read().enableVersion3) context.read().languageProfiles, + ]); + setState(() {}); + } + + @override + Widget build(BuildContext context) => Scaffold( + key: _scaffoldKey, + appBar: _appBar, + body: _loaded ? _body : LSLoader(), + ); + + Widget get _appBar => LunaAppBar( + context: context, + title: 'Add Series', + popUntil: '/sonarr', + actions: [ + SonarrSeriesAddDetailsAppbarLinkAction(tvdbId: widget.tvdbId), + ], + ); + + Widget get _body => LSRefreshIndicator( + refreshKey: _refreshKey, + onRefresh: _refresh, + child: FutureBuilder( + future: Future.wait([ + context.watch().seriesLookup, + context.watch().rootFolders, + context.watch().qualityProfiles, + if(context.watch().enableVersion3) + context.watch().languageProfiles, + ]), + builder: (context, AsyncSnapshot> snapshot) { + if(snapshot.hasError) return LSErrorMessage(onTapHandler: () => _refresh()); + if(snapshot.hasData) { + SonarrSeriesLookup series = (snapshot.data[0] as List).firstWhere( + (series) => series?.tvdbId == widget.tvdbId, + orElse: () => null, + ); + if(series != null) return _list( + series: series, + rootFolders: snapshot.data[1], + qualityProfiles: snapshot.data[2], + languageProfiles: snapshot.data.length == 3 ? null : snapshot.data[3], + ); + return _unknown; + } + return LSLoader(); + }, + ), + ); + + Widget _list({ + @required SonarrSeriesLookup series, + @required List rootFolders, + @required List qualityProfiles, + @required List languageProfiles, + }) => LSListView( + children: [ + SonarrSeriesAddSearchResultTile(series: series, onTapShowOverview: true, exists: false), + SonarrSeriesAddDetailsMonitoredTile(series: series), + SonarrSeriesAddDetailsUseSeasonFoldersTile(series: series), + SonarrSeriesAddDetailsSeriesTypeTile(series: series), + SonarrSeriesAddDetailsMonitorStatusTile(series: series), + SonarrSeriesAddDetailsRootFolderTile(series: series, rootFolder: rootFolders), + SonarrSeriesAddDetailsQualityProfileTile(series: series, profiles: qualityProfiles), + if(Provider.of(context).enableVersion3) + SonarrSeriesAddDetailsLanguageProfileTile(series: series, profiles: languageProfiles), + SonarrSeriesAddDetailsAddSeriesButton(series: series, rootFolders: rootFolders), + ], + ); + + Widget get _unknown => LSGenericMessage(text: 'Series Not Found'); +} diff --git a/lib/modules/sonarr/modules/series_add_details/widgets.dart b/lib/modules/sonarr/modules/series_add_details/widgets.dart new file mode 100644 index 00000000..f45a6b4f --- /dev/null +++ b/lib/modules/sonarr/modules/series_add_details/widgets.dart @@ -0,0 +1,9 @@ +export 'widgets/appbar_link_action.dart'; +export 'widgets/button_add_series.dart'; +export 'widgets/tile_language_profile.dart'; +export 'widgets/tile_monitor_status.dart'; +export 'widgets/tile_monitored.dart'; +export 'widgets/tile_quality_profile.dart'; +export 'widgets/tile_root_folder.dart'; +export 'widgets/tile_series_type.dart'; +export 'widgets/tile_use_season_folders.dart'; diff --git a/lib/modules/sonarr/modules/series_add_details/widgets/appbar_link_action.dart b/lib/modules/sonarr/modules/series_add_details/widgets/appbar_link_action.dart new file mode 100644 index 00000000..2d425544 --- /dev/null +++ b/lib/modules/sonarr/modules/series_add_details/widgets/appbar_link_action.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; + +class SonarrSeriesAddDetailsAppbarLinkAction extends StatelessWidget { + final int tvdbId; + + SonarrSeriesAddDetailsAppbarLinkAction({ + Key key, + @required this.tvdbId, + }) : super(key: key); + @override + Widget build(BuildContext context) => LSIconButton( + icon: Icons.link, + onPressed: () async => tvdbId?.toString()?.lsLinks_OpenTVDB(), + ); +} \ No newline at end of file diff --git a/lib/modules/sonarr/modules/series_add_details/widgets/button_add_series.dart b/lib/modules/sonarr/modules/series_add_details/widgets/button_add_series.dart new file mode 100644 index 00000000..19e835e9 --- /dev/null +++ b/lib/modules/sonarr/modules/series_add_details/widgets/button_add_series.dart @@ -0,0 +1,156 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesAddDetailsAddSeriesButton extends StatefulWidget { + final SonarrSeriesLookup series; + final List rootFolders; + + SonarrSeriesAddDetailsAddSeriesButton({ + Key key, + @required this.series, + @required this.rootFolders, + }) : super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State { + LunaLoadingState _state = LunaLoadingState.INACTIVE; + + @override + Widget build(BuildContext context) => Padding( + child: Row( + children: [ + Expanded( + child: Card( + child: InkWell( + child: ListTile( + title: _state == LunaLoadingState.INACTIVE + ? Text( + 'Add', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: Constants.UI_FONT_SIZE_STICKYHEADER, + ), + textAlign: TextAlign.center, + ) + : LSLoader( + color: Colors.white, + size: 20.0, + ), + ), + borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), + onTap: _state == LunaLoadingState.INACTIVE + ? () async => _onTap(false) + : null, + ), + color: LunaColours.accent, + margin: EdgeInsets.all(6.0), + elevation: Constants.UI_ELEVATION, + shape: LSRoundedShape(), + ), + ), + Expanded( + child: Card( + child: InkWell( + child: ListTile( + title: _state == LunaLoadingState.INACTIVE + ? Text( + 'Add + Search', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: Constants.UI_FONT_SIZE_STICKYHEADER, + ), + textAlign: TextAlign.center, + ) + : LSLoader( + color: Colors.white, + size: 20.0, + ), + ), + borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), + onTap: _state == LunaLoadingState.INACTIVE + ? () async => _onTap(true) + : null, + ), + color: LunaColours.orange, + margin: EdgeInsets.all(6.0), + elevation: Constants.UI_ELEVATION, + shape: LSRoundedShape(), + ), + ), + ], + ), + padding: EdgeInsets.symmetric(horizontal: 6.0), + ); + + Future _onTap(bool search) async { + if(context.read().api != null) { + setState(() => _state = LunaLoadingState.ACTIVE); + SonarrRootFolder _rootFolder = widget.rootFolders.firstWhere( + (folder) => folder.id == SonarrDatabaseValue.ADD_SERIES_DEFAULT_ROOT_FOLDER.data, + orElse: () => null, + ); + if(_rootFolder != null) { + await context.read().api.series.addSeries( + tvdbId: widget.series.tvdbId, + profileId: SonarrDatabaseValue.ADD_SERIES_DEFAULT_QUALITY_PROFILE.data, + languageProfileId: SonarrDatabaseValue.ADD_SERIES_DEFAULT_LANGUAGE_PROFILE.data, + title: widget.series.title, + titleSlug: widget.series.titleSlug, + images: widget.series.images, + seasons: widget.series.seasons, + rootFolderPath: _rootFolder.path, + tvRageId: widget.series.tvRageId, + seasonFolder: SonarrDatabaseValue.ADD_SERIES_DEFAULT_USE_SEASON_FOLDERS.data, + monitored: SonarrDatabaseValue.ADD_SERIES_DEFAULT_MONITORED.data, + ignoreEpisodesWithFiles: + SonarrDatabaseValue.ADD_SERIES_DEFAULT_MONITOR_STATUS.data == SonarrMonitorStatus.MISSING || + SonarrDatabaseValue.ADD_SERIES_DEFAULT_MONITOR_STATUS.data == SonarrMonitorStatus.FUTURE, + ignoreEpisodesWithoutFiles: + SonarrDatabaseValue.ADD_SERIES_DEFAULT_MONITOR_STATUS.data == SonarrMonitorStatus.EXISTING || + SonarrDatabaseValue.ADD_SERIES_DEFAULT_MONITOR_STATUS.data == SonarrMonitorStatus.FUTURE, + searchForMissingEpisodes: search, + ).then((addedSeries) async { + context.read().resetSeries(); + await context.read().series; + widget.series.id = addedSeries.id; + LSSnackBar( + context: context, + title: search ? 'Series Added (Searching...)' : 'Series Added', + message: widget.series.title, + type: SNACKBAR_TYPE.success, + ); + Navigator.of(context).popAndPushNamed(SonarrSeriesDetailsRouter.route(seriesId: addedSeries.id)); + }) + .catchError((error, stack) { + LunaLogger.error( + 'SonarrSeriesAddDetailsAddSeriesButton', + '_onTap', + 'Failed to add series: ${widget.series.id}', + error, + stack, + uploadToSentry: !(error is DioError), + ); + LSSnackBar( + context: context, + title: 'Failed to Add Series', + type: SNACKBAR_TYPE.failure, + ); + }); + } else { + LSSnackBar( + context: context, + title: 'Invalid Root Folder', + message: 'Please select a valid root folder', + type: SNACKBAR_TYPE.failure, + ); + } + setState(() => _state = LunaLoadingState.INACTIVE); + } + } +} diff --git a/lib/modules/sonarr/modules/series_add_details/widgets/tile_language_profile.dart b/lib/modules/sonarr/modules/series_add_details/widgets/tile_language_profile.dart new file mode 100644 index 00000000..a1007ab5 --- /dev/null +++ b/lib/modules/sonarr/modules/series_add_details/widgets/tile_language_profile.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesAddDetailsLanguageProfileTile extends StatefulWidget { + final SonarrSeriesLookup series; + final List profiles; + + SonarrSeriesAddDetailsLanguageProfileTile({ + Key key, + @required this.series, + @required this.profiles, + }) : super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State { + @override + Widget build(BuildContext context) => LSCardTile( + title: LSTitle(text: 'Language Profile'), + subtitle: ValueListenableBuilder( + valueListenable: Database.lunaSeaBox.listenable(keys: [SonarrDatabaseValue.ADD_SERIES_DEFAULT_LANGUAGE_PROFILE.key]), + builder: (context, box, _) => LSSubtitle( + text: widget.profiles.firstWhere( + (element) => element.id == SonarrDatabaseValue.ADD_SERIES_DEFAULT_LANGUAGE_PROFILE.data, + orElse: () => null, + )?.name ?? Constants.TEXT_EMDASH, + ), + ), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: _onTap, + ); + + Future _onTap() async { + List _values = await SonarrDialogs.editLanguageProfiles(context, widget.profiles); + if(_values[0]) { + SonarrLanguageProfile _profile = _values[1]; + widget.series.languageProfileId = _profile.id; + SonarrDatabaseValue.ADD_SERIES_DEFAULT_LANGUAGE_PROFILE.put(_profile.id); + } + } +} diff --git a/lib/modules/sonarr/modules/series_add_details/widgets/tile_monitor_status.dart b/lib/modules/sonarr/modules/series_add_details/widgets/tile_monitor_status.dart new file mode 100644 index 00000000..348eee9a --- /dev/null +++ b/lib/modules/sonarr/modules/series_add_details/widgets/tile_monitor_status.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesAddDetailsMonitorStatusTile extends StatefulWidget { + final SonarrSeriesLookup series; + + SonarrSeriesAddDetailsMonitorStatusTile({ + Key key, + @required this.series, + }) : super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State { + @override + Widget build(BuildContext context) => LSCardTile( + title: LSTitle(text: 'Monitor Status'), + subtitle: ValueListenableBuilder( + valueListenable: Database.lunaSeaBox.listenable(keys: [SonarrDatabaseValue.ADD_SERIES_DEFAULT_MONITOR_STATUS.key]), + builder: (context, box, _) => LSSubtitle( + text: (SonarrDatabaseValue.ADD_SERIES_DEFAULT_MONITOR_STATUS.data as SonarrMonitorStatus).name, + ), + ), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: _onTap, + ); + + Future _onTap() async { + List _values = await SonarrDialogs.editMonitorStatus(context); + if(_values[0]) { + SonarrMonitorStatus _status = _values[1]; + _status.process(widget.series.seasons); + SonarrDatabaseValue.ADD_SERIES_DEFAULT_MONITOR_STATUS.put(_values[1]); + } + } +} diff --git a/lib/modules/sonarr/modules/series_add_details/widgets/tile_monitored.dart b/lib/modules/sonarr/modules/series_add_details/widgets/tile_monitored.dart new file mode 100644 index 00000000..25b391ee --- /dev/null +++ b/lib/modules/sonarr/modules/series_add_details/widgets/tile_monitored.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesAddDetailsMonitoredTile extends StatefulWidget { + final SonarrSeriesLookup series; + + SonarrSeriesAddDetailsMonitoredTile({ + Key key, + @required this.series, + }) : super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State { + @override + Widget build(BuildContext context) => LSCardTile( + title: LSTitle(text: 'Monitored'), + subtitle: LSSubtitle(text: 'Monitor series for new releases'), + trailing: Switch( + value: widget.series.monitored, + onChanged: (value) { + setState(() => widget.series.monitored = value); + SonarrDatabaseValue.ADD_SERIES_DEFAULT_MONITORED.put(value); + }, + ), + ); +} diff --git a/lib/modules/sonarr/modules/series_add_details/widgets/tile_quality_profile.dart b/lib/modules/sonarr/modules/series_add_details/widgets/tile_quality_profile.dart new file mode 100644 index 00000000..b6deb29d --- /dev/null +++ b/lib/modules/sonarr/modules/series_add_details/widgets/tile_quality_profile.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesAddDetailsQualityProfileTile extends StatefulWidget { + final SonarrSeriesLookup series; + final List profiles; + + SonarrSeriesAddDetailsQualityProfileTile({ + Key key, + @required this.series, + @required this.profiles, + }) : super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State { + @override + Widget build(BuildContext context) => LSCardTile( + title: LSTitle(text: 'Quality Profile'), + subtitle: ValueListenableBuilder( + valueListenable: Database.lunaSeaBox.listenable(keys: [SonarrDatabaseValue.ADD_SERIES_DEFAULT_QUALITY_PROFILE.key]), + builder: (context, box, _) => LSSubtitle( + text: widget.profiles.firstWhere( + (element) => element.id == SonarrDatabaseValue.ADD_SERIES_DEFAULT_QUALITY_PROFILE.data, + orElse: () => null, + )?.name ?? Constants.TEXT_EMDASH, + ), + ), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: _onTap, + ); + + Future _onTap() async { + List _values = await SonarrDialogs.editQualityProfile(context, widget.profiles); + if(_values[0]) { + SonarrQualityProfile _profile = _values[1]; + widget.series.profileId = _profile.id; + widget.series.qualityProfileId = _profile.id; + SonarrDatabaseValue.ADD_SERIES_DEFAULT_QUALITY_PROFILE.put(_profile.id); + } + } +} diff --git a/lib/modules/sonarr/modules/series_add_details/widgets/tile_root_folder.dart b/lib/modules/sonarr/modules/series_add_details/widgets/tile_root_folder.dart new file mode 100644 index 00000000..7eeb9f4e --- /dev/null +++ b/lib/modules/sonarr/modules/series_add_details/widgets/tile_root_folder.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesAddDetailsRootFolderTile extends StatefulWidget { + final SonarrSeriesLookup series; + final List rootFolder; + + SonarrSeriesAddDetailsRootFolderTile({ + Key key, + @required this.series, + @required this.rootFolder, + }) : super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State { + @override + Widget build(BuildContext context) => LSCardTile( + title: LSTitle(text: 'Root Folder'), + subtitle: ValueListenableBuilder( + valueListenable: Database.lunaSeaBox.listenable(keys: [SonarrDatabaseValue.ADD_SERIES_DEFAULT_ROOT_FOLDER.key]), + builder: (context, box, _) => LSSubtitle( + text: widget.rootFolder.firstWhere( + (element) => element.id == SonarrDatabaseValue.ADD_SERIES_DEFAULT_ROOT_FOLDER.data, + orElse: () => null, + )?.path ?? Constants.TEXT_EMDASH, + ), + ), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: _onTap, + ); + + Future _onTap() async { + List _values = await SonarrDialogs.editRootFolder(context, widget.rootFolder); + if(_values[0]) { + SonarrRootFolder _folder = _values[1]; + widget.series.rootFolderPath = _folder.path; + SonarrDatabaseValue.ADD_SERIES_DEFAULT_ROOT_FOLDER.put(_folder.id); + } + } +} diff --git a/lib/modules/sonarr/modules/series_add_details/widgets/tile_series_type.dart b/lib/modules/sonarr/modules/series_add_details/widgets/tile_series_type.dart new file mode 100644 index 00000000..ce02d7a0 --- /dev/null +++ b/lib/modules/sonarr/modules/series_add_details/widgets/tile_series_type.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesAddDetailsSeriesTypeTile extends StatefulWidget { + final SonarrSeriesLookup series; + + SonarrSeriesAddDetailsSeriesTypeTile({ + Key key, + @required this.series, + }) : super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State { + @override + Widget build(BuildContext context) => LSCardTile( + title: LSTitle(text: 'Series Type'), + subtitle: ValueListenableBuilder( + valueListenable: Database.lunaSeaBox.listenable(keys: [SonarrDatabaseValue.ADD_SERIES_DEFAULT_SERIES_TYPE.key]), + builder: (context, box, _) => LSSubtitle( + text: SonarrSeriesType.STANDARD.from(SonarrDatabaseValue.ADD_SERIES_DEFAULT_SERIES_TYPE.data)?.value?.lsLanguage_Capitalize() ?? Constants.TEXT_EMDASH, + ), + ), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: _onTap, + ); + + Future _onTap() async { + List _values = await SonarrDialogs.editSeriesType(context); + if(_values[0]) { + SonarrSeriesType _type = _values[1]; + widget.series.seriesType = _type; + SonarrDatabaseValue.ADD_SERIES_DEFAULT_SERIES_TYPE.put(_type.value); + } + } +} diff --git a/lib/modules/sonarr/modules/series_add_details/widgets/tile_use_season_folders.dart b/lib/modules/sonarr/modules/series_add_details/widgets/tile_use_season_folders.dart new file mode 100644 index 00000000..6aed0b68 --- /dev/null +++ b/lib/modules/sonarr/modules/series_add_details/widgets/tile_use_season_folders.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesAddDetailsUseSeasonFoldersTile extends StatefulWidget { + final SonarrSeriesLookup series; + + SonarrSeriesAddDetailsUseSeasonFoldersTile({ + Key key, + @required this.series, + }) : super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State { + @override + Widget build(BuildContext context) => LSCardTile( + title: LSTitle(text: 'Use Season Folders'), + subtitle: LSSubtitle(text: 'Sort episodes into season folders'), + trailing: Switch( + value: widget.series.seasonFolder, + onChanged: (value) { + setState(() => widget.series.seasonFolder = value); + SonarrDatabaseValue.ADD_SERIES_DEFAULT_USE_SEASON_FOLDERS.put(value); + }, + ), + ); +} diff --git a/lib/modules/sonarr/modules/series_details.dart b/lib/modules/sonarr/modules/series_details.dart new file mode 100644 index 00000000..3536840a --- /dev/null +++ b/lib/modules/sonarr/modules/series_details.dart @@ -0,0 +1,2 @@ +export 'series_details/route.dart'; +export 'series_details/widgets.dart'; diff --git a/lib/modules/sonarr/modules/series_details/route.dart b/lib/modules/sonarr/modules/series_details/route.dart new file mode 100644 index 00000000..ea47550d --- /dev/null +++ b/lib/modules/sonarr/modules/series_details/route.dart @@ -0,0 +1,182 @@ +import 'package:fluro_fork/fluro_fork.dart'; +import 'package:flutter/material.dart' hide Router; +import 'package:flutter/scheduler.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; +import 'package:tuple/tuple.dart'; + +class SonarrSeriesDetailsRouter { + static const String ROUTE_NAME = '/sonarr/series/details/:seriesid'; + + static Future navigateTo(BuildContext context, { + @required int seriesId, + }) async => LunaRouter.router.navigateTo( + context, + route(seriesId: seriesId), + ); + + static String route({ @required int seriesId }) => ROUTE_NAME + .replaceFirst(':seriesid', seriesId?.toString() ?? '-1'); + + static void defineRoutes(Router router) { + router.define( + ROUTE_NAME, + handler: Handler(handlerFunc: (context, params) => _SonarrSeriesDetailsRoute( + seriesId: int.tryParse(params['seriesid'][0]) ?? -1, + )), + transitionType: LunaRouter.transitionType, + ); + } +} + +class _SonarrSeriesDetailsRoute extends StatefulWidget { + final int seriesId; + + _SonarrSeriesDetailsRoute({ + Key key, + @required this.seriesId, + }): super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State<_SonarrSeriesDetailsRoute> { + final GlobalKey _scaffoldKey = GlobalKey(); + PageController _pageController; + + @override + void initState() { + super.initState(); + _pageController = PageController(initialPage: SonarrDatabaseValue.NAVIGATION_INDEX_SERIES_DETAILS.data); + SchedulerBinding.instance.scheduleFrameCallback((_) => _refresh()); + } + + Future _refresh() async { + SonarrState _state = Provider.of(context, listen: false); + SonarrSeries _series = await _state.api.series.getSeries(seriesId: widget.seriesId); + List allSeries = await _state.series; + int _index = allSeries?.indexWhere((element) => element.id == widget.seriesId) ?? -1; + if(_index >= 0) allSeries[_index] = _series; + _state.notify(); + if(mounted) setState(() {}); + } + + SonarrSeries _findSeries(List series) { + return series.firstWhere( + (series) => series.id == widget.seriesId, + orElse: () => null, + ); + } + + List _findTags(List tagIds, List tags) { + return tags.where((tag) => tagIds.contains(tag.id)).toList(); + } + + SonarrQualityProfile _findQualityProfile(int profileId, List profiles) { + return profiles.firstWhere( + (profile) => profile.id == profileId, + orElse: () => null, + ); + } + + SonarrLanguageProfile _findLanguageProfile(int languageProfileId, List profiles) { + if(!Provider.of(context, listen: false).enableVersion3) return null; + return profiles.firstWhere( + (profile) => profile.id == languageProfileId, + orElse: () => null, + ); + } + + @override + Widget build(BuildContext context) => Scaffold( + key: _scaffoldKey, + appBar: _appBar, + bottomNavigationBar: _bottomNavigationBar, + body: _body, + ); + + Widget get _appBar => LunaAppBar( + context: context, + title: 'Series Details', + popUntil: '/sonarr', + actions: [ + SonarrAppBarSeriesSettingsAction(seriesId: widget.seriesId), + ], + ); + + Widget get _bottomNavigationBar => SonarrSeriesDetailsNavigationBar(pageController: _pageController); + + Widget get _body => Selector>, + Future>, + Future>, + Future>, + bool + >>( + selector: (_, state) => Tuple5( + state.series, + state.tags, + state.qualityProfiles, + state.languageProfiles, + state.enableVersion3, + ), + builder: (context, tuple, _) => FutureBuilder( + future: Future.wait([ + tuple.item1, + tuple.item2, + tuple.item3, + if(tuple.item5) tuple.item4, + ]), + builder: (context, AsyncSnapshot> snapshot) { + if(snapshot.hasError) { + if(snapshot.connectionState != ConnectionState.waiting) { + LunaLogger.error( + '_SonarrSeriesDetailsRoute', + '_body', + 'Unable to pull Sonarr series details', + snapshot.error, + null, + uploadToSentry: !(snapshot.error is DioError), + ); + } + return LSErrorMessage(onTapHandler: () => _refresh()); + } + if(snapshot.hasData) { + SonarrSeries series = _findSeries(snapshot.data[0]); + if(series != null) { + SonarrQualityProfile quality = _findQualityProfile(series.profileId, snapshot.data[2]); + SonarrLanguageProfile language = Provider.of(context, listen: false).enableVersion3 + ? _findLanguageProfile(series.languageProfileId, snapshot.data[3]) + : null; + List tags = _findTags(series.tags, snapshot.data[1]); + return series == null + ? _unknown + : PageView( + controller: _pageController, + children: _tabs( + series: series, + quality: quality, + language: language, + tags: tags, + ), + ); + } + } + return LSLoader(); + }, + ), + ); + + List _tabs({ + @required SonarrSeries series, + @required SonarrQualityProfile quality, + @required SonarrLanguageProfile language, + @required List tags, + }) => [ + SonarrSeriesDetailsOverview(series: series, quality: quality, language: language, tags: tags), + SonarrSeriesDetailsSeasonList(series: series), + ]; + + Widget get _unknown => LSGenericMessage(text: 'Series Not Found'); +} diff --git a/lib/modules/sonarr/modules/series_details/widgets.dart b/lib/modules/sonarr/modules/series_details/widgets.dart new file mode 100644 index 00000000..e6957386 --- /dev/null +++ b/lib/modules/sonarr/modules/series_details/widgets.dart @@ -0,0 +1,6 @@ +export 'widgets/appbar_edit_action.dart'; +export 'widgets/navigation_bar.dart'; +export 'widgets/overview.dart'; +export 'widgets/season_all_tile.dart'; +export 'widgets/season_list.dart'; +export 'widgets/season_tile.dart'; diff --git a/lib/modules/sonarr/modules/series_details/widgets/appbar_edit_action.dart b/lib/modules/sonarr/modules/series_details/widgets/appbar_edit_action.dart new file mode 100644 index 00000000..73c4c232 --- /dev/null +++ b/lib/modules/sonarr/modules/series_details/widgets/appbar_edit_action.dart @@ -0,0 +1,160 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrAppBarSeriesSettingsAction extends StatelessWidget { + final int seriesId; + + SonarrAppBarSeriesSettingsAction({ + Key key, + @required this.seriesId, + }) : super(key: key); + + @override + Widget build(BuildContext context) => Selector>>( + selector: (_, state) => state.series, + builder: (context, future, _) => FutureBuilder( + future: future, + builder: (context, AsyncSnapshot> snapshot) { + if(snapshot.hasError) return Container(); + if(snapshot.hasData) { + SonarrSeries series = snapshot.data.firstWhere((element) => element.id == seriesId, orElse: () => null); + if(series != null) return LSIconButton( + icon: Icons.edit, + onPressed: () async => handler(context, series), + ); + } + return Container(); + }, + ), + ); + + static Future handler( + BuildContext context, + SonarrSeries series, + ) async { + List values = await SonarrDialogs.seriesSettings(context, series); + if(values[0]) switch(values[1] as SonarrSeriesSettingsType) { + case SonarrSeriesSettingsType.EDIT: _edit(context, series); break; + case SonarrSeriesSettingsType.DELETE: _delete(context, series); break; + case SonarrSeriesSettingsType.REFRESH: _refresh(context, series); break; + case SonarrSeriesSettingsType.MONITORED: _monitored(context, series); break; + default: LunaLogger.warning('SonarrAppBarSeriesSettingsAction', '_handler', 'Unknown case: ${(values[1] as SonarrSeriesSettingsType)}'); + } + } + + static Future _edit( + BuildContext context, + SonarrSeries series, + ) async => SonarrSeriesEditRouter.navigateTo(context, seriesId: series.id); + + static Future _monitored( + BuildContext context, + SonarrSeries series, + ) async { + SonarrState _state = context.read(); + if(_state.api != null) { + SonarrSeries _series = series.clone(); + _series.monitored = !_series.monitored; + _state.api.series.updateSeries(series: _series) + .then((_) { + series.monitored = !series.monitored; + _state.notify(); + LSSnackBar( + context: context, + title: series.monitored + ? 'Monitoring' + : 'No Longer Monitoring', + message: series.title, + type: SNACKBAR_TYPE.success, + ); + }) + .catchError((error, stack) { + LunaLogger.error( + 'SonarrAppBarSeriesSettingsAction', + '_monitored', + 'Failed to toggle monitored state for series: ${series.id} / ${series.monitored}', + error, + stack, + uploadToSentry: !(error is DioError), + ); + LSSnackBar( + context: context, + title: series.monitored + ? 'Failed to Unmonitor Series' + : 'Failed to Monitor Series', + type: SNACKBAR_TYPE.failure, + ); + }); + } + } + + static Future _refresh( + BuildContext context, + SonarrSeries series, + ) async { + Sonarr _sonarr = Provider.of(context, listen: false).api; + if(_sonarr != null) _sonarr.command.refreshSeries(seriesId: series.id) + .then((_) { + LSSnackBar( + context: context, + title: 'Refreshing...', + message: series.title, + ); + }) + .catchError((error, stack) { + LunaLogger.error( + 'SonarrAppBarSeriesSettingsAction', + '_refresh', + 'Unable to refresh series: ${series.id}', + error, + stack, + uploadToSentry: !(error is DioError), + ); + LSSnackBar( + context: context, + title: 'Failed to Refresh', + type: SNACKBAR_TYPE.failure, + ); + }); + } + + static Future _delete( + BuildContext context, + SonarrSeries series, + ) async { + SonarrState _state = context.read(); + List _values = await SonarrDialogs.confirmDeleteSeries(context); + if(_state.api != null && _values[0]) _state.api.series.deleteSeries( + seriesId: series.id, + deleteFiles: _state.removeSeriesDeleteFiles, + ) + .then((_) { + LSSnackBar( + context: context, + title: _state.removeSeriesDeleteFiles + ? 'Series Removed (With Data)' + : 'Series Removed', + message: series.title, + type: SNACKBAR_TYPE.success, + ); + _state.reset(); + Navigator.of(context).pop(); + }) + .catchError((error, stack) { + LunaLogger.error( + 'SonarrAppBarSeriesSettingsAction', + '_delete', + 'Failed to remove series: ${series.id}', + error, + stack, + uploadToSentry: !(error is DioError), + ); + LSSnackBar( + context: context, + title: 'Failed to Remove Series', + type: SNACKBAR_TYPE.failure, + ); + }); + } +} diff --git a/lib/modules/sonarr/modules/series_details/widgets/navigation_bar.dart b/lib/modules/sonarr/modules/series_details/widgets/navigation_bar.dart new file mode 100644 index 00000000..0555bd8a --- /dev/null +++ b/lib/modules/sonarr/modules/series_details/widgets/navigation_bar.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesDetailsNavigationBar extends StatefulWidget { + static const List icons = [ + Icons.subject, + CustomIcons.television, + ]; + + static const List titles = [ + 'Overview', + 'Seasons', + ]; + + final PageController pageController; + + SonarrSeriesDetailsNavigationBar({ + Key key, + @required this.pageController, + }): super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State { + int _index = SonarrDatabaseValue.NAVIGATION_INDEX_SERIES_DETAILS.data; + + @override + void initState() { + super.initState(); + widget.pageController?.addListener(_pageControllerListener); + } + + @override + void dispose() { + widget.pageController?.removeListener(_pageControllerListener); + super.dispose(); + } + + void _pageControllerListener() { + if(widget.pageController.page.round() == _index) return; + setState(() => _index = widget.pageController.page.round()); + } + + @override + Widget build(BuildContext context) => LSBottomNavigationBar( + index: _index, + onTap: _navOnTap, + icons: SonarrSeriesDetailsNavigationBar.icons, + titles: SonarrSeriesDetailsNavigationBar.titles, + ); + + Future _navOnTap(int index) async { + if(widget.pageController.hasClients) widget.pageController.animateToPage( + index, + duration: Duration(milliseconds: Constants.UI_NAVIGATION_SPEED), + curve: Curves.easeOutSine, + ); + } +} diff --git a/lib/modules/sonarr/modules/series_details/widgets/overview.dart b/lib/modules/sonarr/modules/series_details/widgets/overview.dart new file mode 100644 index 00000000..f595c833 --- /dev/null +++ b/lib/modules/sonarr/modules/series_details/widgets/overview.dart @@ -0,0 +1,153 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesDetailsOverview extends StatelessWidget { + final SonarrSeries series; + final SonarrQualityProfile quality; + final SonarrLanguageProfile language; + final List tags; + + final double _height = 105.0; + final double _width = 70.0; + final double _padding = 8.0; + + SonarrSeriesDetailsOverview({ + Key key, + @required this.series, + @required this.quality, + @required this.language, + @required this.tags, + }) : super(key: key); + + @override + Widget build(BuildContext context) => LSListView( + children: [ + _description(context), + _information(context), + _links, + ], + ); + + Widget _information(BuildContext context) => LSTableBlock( + children: [ + LSTableContent(title: 'path', body: series.path ?? 'Unknown'), + LSTableContent(title: 'size', body: series.sizeOnDisk?.lsBytes_BytesToString(decimals: 1) ?? 'Unknown'), + LSTableContent(title: 'type', body: series.seriesType?.value?.lsLanguage_Capitalize() ?? 'Unknown'), + LSTableContent(title: 'quality', body: quality?.name ?? 'Unknown'), + if(Provider.of(context, listen: false).enableVersion3) LSTableContent(title: 'language', body: language?.name ?? Constants.TEXT_EMDASH), + if(tags != null && tags.length > 0) LSTableContent( + title: 'tags', + body: tags.fold('', (string, tag) => string += ', ${tag.label}').substring(2), + ), + LSTableContent(title: '', body: ''), + LSTableContent(title: 'status', body: series.status?.lsLanguage_Capitalize() ?? 'Unknown'), + LSTableContent(title: 'runtime', body: series.lunaRuntime), + LSTableContent(title: 'network', body: series.network ?? 'Unknown'), + if(series.nextAiring != null) LSTableContent(title: 'next airing', body: series.lunaNextAiring), + if(series.nextAiring != null) LSTableContent(title: 'air time', body: series.lunaAirTime), + ], + ); + + Widget _description(BuildContext context) => LSCard( + child: InkWell( + child: Row( + children: [ + LSNetworkImage( + url: Provider.of(context, listen: false).getPosterURL(series.id), + headers: Provider.of(context, listen: false).headers.cast(), + placeholder: 'assets/images/sonarr/noseriesposter.png', + height: _height, + width: _width, + ), + Expanded( + child: Padding( + child: Container( + child: Column( + children: [ + LSTitle(text: series.title, maxLines: 1), + Text( + series.overview, + maxLines: 4, + overflow: TextOverflow.fade, + style: TextStyle( + color: Colors.white70, + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + ), + ), + ], + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + ), + height: (_height-(_padding*2)), + ), + padding: EdgeInsets.all(_padding), + ), + ), + ], + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + ), + borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), + onTap: () async => LunaDialogs.textPreview(context, series.title, series.overview), + ), + decoration: LSCardBackground( + uri: Provider.of(context, listen: false).getFanartURL(series.id), + headers: Provider.of(context, listen: false).headers, + ), + ); + + Widget get _links => LSContainerRow( + children: [ + if(series.imdbId != '') Expanded( + child: LSCard( + child: InkWell( + child: Padding( + child: Image.asset( + 'assets/images/services/imdb.png', + height: 21.0, + ), + padding: EdgeInsets.all(18.0), + ), + borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), + onTap: () async => await series?.imdbId?.lsLinks_OpenIMDB(), + ), + reducedMargin: true, + ), + ), + if(series.tvdbId != 0) Expanded( + child: LSCard( + child: InkWell( + child: Padding( + child: Image.asset( + 'assets/images/services/thetvdb.png', + height: 23.0, + ), + padding: EdgeInsets.all(17.0), + ), + borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), + onTap: () async => await series?.tvdbId?.toString()?.lsLinks_OpenTVDB(), + ), + reducedMargin: true, + ), + ), + if(series.tvMazeId != 0) Expanded( + child: LSCard( + child: InkWell( + child: Padding( + child: Image.asset( + 'assets/images/services/tvmaze.png', + height: 21.0, + ), + padding: EdgeInsets.all(18.0), + ), + borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), + onTap: () async => await series?.tvMazeId?.toString()?.lsLinks_OpenTVMaze(), + ), + reducedMargin: true, + ), + ), + ], + ); +} diff --git a/lib/modules/sonarr/modules/series_details/widgets/season_all_tile.dart b/lib/modules/sonarr/modules/series_details/widgets/season_all_tile.dart new file mode 100644 index 00000000..e43e9212 --- /dev/null +++ b/lib/modules/sonarr/modules/series_details/widgets/season_all_tile.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesDetailsSeasonAllTile extends StatelessWidget { + final SonarrSeries series; + + SonarrSeriesDetailsSeasonAllTile({ + Key key, + @required this.series, + }) : super(key: key); + + @override + Widget build(BuildContext context) => LSCardTile( + title: LSTitle(text: 'All Seasons', darken: !series.monitored), + subtitle: RichText( + text: TextSpan( + style: TextStyle( + color: series.monitored ? Colors.white70 : Colors.white30, + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + ), + children: [ + TextSpan(text: series?.sizeOnDisk?.lsBytes_BytesToString(decimals: 1) ?? '0.0 B'), + TextSpan(text: '\n'), + TextSpan( + style: TextStyle( + color: series.lunaPercentageComplete == 100 + ? series.monitored ? LunaColours.accent : LunaColours.accent.withOpacity(0.30) + : series.monitored ? LunaColours.red : LunaColours.red.withOpacity(0.30), + fontWeight: FontWeight.w600, + ), + text: '${series.lunaPercentageComplete}% ${Constants.TEXT_EMDASH} ${series.episodeFileCount ?? 0}/${series.episodeCount ?? 0} Episodes Available', + ), + ], + ), + ), + padContent: true, + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: () async => _onTap(context), + ); + + Future _onTap(BuildContext context) async => SonarrSeriesSeasonDetailsRouter.navigateTo( + context, + seriesId: series.id, + seasonNumber: -1, + ); +} diff --git a/lib/modules/sonarr/modules/series_details/widgets/season_list.dart b/lib/modules/sonarr/modules/series_details/widgets/season_list.dart new file mode 100644 index 00000000..e283d22d --- /dev/null +++ b/lib/modules/sonarr/modules/series_details/widgets/season_list.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesDetailsSeasonList extends StatelessWidget { + final SonarrSeries series; + + SonarrSeriesDetailsSeasonList({ + Key key, + @required this.series, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + if(series.seasons.length == 0) return LSGenericMessage(text: 'No Seasons Found'); + List _seasons = series.seasons; + _seasons.sort((a,b) => a.seasonNumber.compareTo(b.seasonNumber)); + return LSListView( + children: [ + if(_seasons.length > 1) SonarrSeriesDetailsSeasonAllTile(series: series), + ...List.generate( + _seasons.length, + (index) => SonarrSeriesDetailsSeasonTile( + seriesId: series.id, + season: series.seasons[_seasons.length - 1 - index], + ), + ), + ], + ); + } +} diff --git a/lib/modules/sonarr/modules/series_details/widgets/season_tile.dart b/lib/modules/sonarr/modules/series_details/widgets/season_tile.dart new file mode 100644 index 00000000..f3de6fe5 --- /dev/null +++ b/lib/modules/sonarr/modules/series_details/widgets/season_tile.dart @@ -0,0 +1,108 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesDetailsSeasonTile extends StatefulWidget { + final SonarrSeriesSeason season; + final int seriesId; + + SonarrSeriesDetailsSeasonTile({ + Key key, + @required this.season, + @required this.seriesId, + }) : super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State { + @override + Widget build(BuildContext context) => widget.season == null + ? Container() + : LSCardTile( + title: LSTitle(text: widget.season.seasonNumber == 0 ? 'Specials' : 'Season ${widget.season.seasonNumber}', darken: !widget.season.monitored), + subtitle: RichText( + text: TextSpan( + style: TextStyle( + color: widget.season.monitored ? Colors.white70 : Colors.white30, + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + ), + children: [ + TextSpan(text: widget.season?.statistics?.sizeOnDisk?.lsBytes_BytesToString(decimals: 1) ?? '0.0 B'), + TextSpan(text: '\n'), + TextSpan( + style: TextStyle( + color: widget.season.lunaPercentageComplete == 100 + ? widget.season.monitored ? LunaColours.accent : LunaColours.accent.withOpacity(0.30) + : widget.season.monitored ? LunaColours.red : LunaColours.red.withOpacity(0.30), + fontWeight: FontWeight.w600, + ), + text: '${widget.season.lunaPercentageComplete}% ${Constants.TEXT_EMDASH} ${widget?.season?.statistics?.episodeFileCount ?? 0}/${widget?.season?.statistics?.totalEpisodeCount ?? 0} Episodes Available', + ), + ], + ), + ), + trailing: _trailing(context), + onTap: () async => _onTap(context), + onLongPress: () async => SonarrSeriesSeasonDetailsSeasonHeader.handler(context, widget.seriesId, widget.season.seasonNumber), + padContent: true, + ); + + Widget _trailing(BuildContext context) => LSIconButton( + icon: widget.season.monitored ? Icons.turned_in : Icons.turned_in_not, + color: widget.season.monitored ? Colors.white : Colors.white30, + onPressed: _trailingOnPressed, + ); + + Future _trailingOnPressed() async { + SonarrState _state = Provider.of(context, listen: false); + bool _fallbackState = widget.season.monitored; + await _state.series + .then((seriesList) { + SonarrSeries _series = seriesList.firstWhere( + (series) => series.id == widget.seriesId, + orElse: () => null, + ); + if(_series == null) throw Exception('Series not found'); + return _series; + }) + .then((series) { + series.seasons.forEach((season) { + if(season.seasonNumber == widget.season.seasonNumber) season.monitored = !widget.season.monitored; + }); + return series; + }) + .then((series) => _state.api.series.updateSeries(series: series)) + .then((_) { + setState(() {}); + LSSnackBar( + context: context, + title: widget.season.monitored + ? 'Monitoring' + : 'No Longer Monitoring', + message: widget.season.seasonNumber == 0 + ? 'Specials' + : 'Season ${widget.season.seasonNumber}', + type: SNACKBAR_TYPE.success, + ); + }) + .catchError((error, stack) { + setState(() => widget.season.monitored = _fallbackState); + LunaLogger.error( + 'SonarrSeriesDetailsSeasonTile', + '_trailingOnPressed', + 'Failed to toggle monitored state: ${widget.seriesId} / ${widget.season.seasonNumber}', + error, + stack, + uploadToSentry: !(error is DioError), + ); + }); + } + + Future _onTap(BuildContext context) async => SonarrSeriesSeasonDetailsRouter.navigateTo( + context, + seriesId: widget.seriesId, + seasonNumber: widget.season.seasonNumber, + ); +} diff --git a/lib/modules/sonarr/modules/series_edit.dart b/lib/modules/sonarr/modules/series_edit.dart new file mode 100644 index 00000000..d7533d4e --- /dev/null +++ b/lib/modules/sonarr/modules/series_edit.dart @@ -0,0 +1,3 @@ +export 'series_edit/route.dart'; +export 'series_edit/state.dart'; +export 'series_edit/widgets.dart'; diff --git a/lib/modules/sonarr/modules/series_edit/route.dart b/lib/modules/sonarr/modules/series_edit/route.dart new file mode 100644 index 00000000..f5225792 --- /dev/null +++ b/lib/modules/sonarr/modules/series_edit/route.dart @@ -0,0 +1,129 @@ +import 'package:fluro_fork/fluro_fork.dart'; +import 'package:flutter/material.dart' hide Router; +import 'package:flutter/scheduler.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesEditRouter { + static const String ROUTE_NAME = '/sonarr/series/edit/:seriesid'; + + static Future navigateTo(BuildContext context, { + @required int seriesId, + }) async => LunaRouter.router.navigateTo( + context, + route(seriesId: seriesId), + ); + + static String route({ @required int seriesId }) => ROUTE_NAME + .replaceFirst(':seriesid', seriesId?.toString() ?? '-1'); + + static void defineRoutes(Router router) { + router.define( + ROUTE_NAME, + handler: Handler(handlerFunc: (context, params) => _SonarrSeriesEditRoute( + seriesId: int.tryParse(params['seriesid'][0]) ?? -1, + )), + transitionType: LunaRouter.transitionType, + ); + } +} + +class _SonarrSeriesEditRoute extends StatefulWidget { + final int seriesId; + + _SonarrSeriesEditRoute({ + Key key, + @required this.seriesId, + }) : super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State<_SonarrSeriesEditRoute> { + final GlobalKey _scaffoldKey = GlobalKey(); + bool _initialLoad = false; + + @override + void initState() { + super.initState(); + SchedulerBinding.instance.scheduleFrameCallback((_) => _refresh()); + } + + Future _refresh() async { + context.read().fetchRootFolders(); + context.read().resetTags(); + context.read().resetQualityProfiles(); + context.read().resetLanguageProfiles(); + setState(() => _initialLoad = true); + } + + @override + Widget build(BuildContext context) => Scaffold( + key: _scaffoldKey, + appBar: _appBar, + body: _initialLoad ? _body : LSLoader(), + ); + + Widget get _appBar => LunaAppBar( + context: context, + title: 'Edit Series', + popUntil: '/sonarr', + ); + + Widget get _body => FutureBuilder( + future: Future.wait([ + Provider.of(context).series, // 0 + Provider.of(context).qualityProfiles, // 1 + if(Provider.of(context).enableVersion3) // 2.? + Provider.of(context).languageProfiles, + ]), + builder: (context, AsyncSnapshot> snapshot) { + if(snapshot.hasError) return LSErrorMessage(onTapHandler: () => _refresh()); + if(snapshot.hasData) { + SonarrSeries series = (snapshot.data[0] as List).firstWhere( + (series) => series?.id == widget.seriesId, + orElse: () => null, + ); + if(series != null) return _list( + series: series, + qualityProfiles: snapshot.data[1], + languageProfiles: snapshot.data.length == 2 ? null : snapshot.data[2], + ); + return _unknown; + } + return LSLoader(); + }, + ); + + Widget _list({ + @required SonarrSeries series, + @required List qualityProfiles, + @required List languageProfiles, + }) => ChangeNotifierProvider( + create: (_) => SonarrSeriesEditState( + series: series, + qualityProfiles: qualityProfiles ?? [], + languageProfiles: languageProfiles ?? [], + ), + builder: (context, _) { + if(context.watch().state == LunaLoadingState.ERROR) + return LSGenericMessage(text: 'An Error Has Occurred'); + return LSListView( + children: [ + SonarrSeriesEditMonitoredTile(), + SonarrSeriesEditSeasonFoldersTile(), + SonarrSeriesEditSeriesPathTile(), + SonarrSeriesEditQualityProfileTile(profiles: qualityProfiles), + context.watch().enableVersion3 + ? SonarrSeriesEditLanguageProfileTile(profiles: languageProfiles) + : Container(), + SonarrSeriesEditSeriesTypeTile(), + SonarrSeriesEditUpdateSeriesButton(series: series), + ], + ); + }, + ); + + Widget get _unknown => LSGenericMessage(text: 'Series Not Found'); +} diff --git a/lib/modules/sonarr/modules/series_edit/state.dart b/lib/modules/sonarr/modules/series_edit/state.dart new file mode 100644 index 00000000..6380a63a --- /dev/null +++ b/lib/modules/sonarr/modules/series_edit/state.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesEditState extends ChangeNotifier { + SonarrSeriesEditState({ + @required SonarrSeries series, + @required List qualityProfiles, + @required List languageProfiles, + }) { + _monitored = series.monitored ?? true; + _useSeasonFolders = series.seasonFolder ?? true; + _seriesPath = series.path; + _seriesType = series.seriesType ?? SonarrSeriesType.STANDARD; + _qualityProfile = qualityProfiles.firstWhere( + (profile) => profile.id == series.profileId, + orElse: () => qualityProfiles.length == 0 ? null : qualityProfiles[0], + ); + _languageProfile = (languageProfiles ?? []).firstWhere( + (profile) => profile.id == series.languageProfileId, + orElse: () => languageProfiles.length == 0 ? null : languageProfiles[0], + ); + } + + LunaLoadingState _state = LunaLoadingState.INACTIVE; + LunaLoadingState get state => _state; + set state(LunaLoadingState state) { + assert(state != null); + _state = state; + notifyListeners(); + } + + bool _monitored = true; + bool get monitored => _monitored; + set monitored(bool monitored) { + assert(monitored != null); + _monitored = monitored; + notifyListeners(); + } + + bool _useSeasonFolders = true; + bool get useSeasonFolders => _useSeasonFolders; + set useSeasonFolders(bool useSeasonFolders) { + assert(useSeasonFolders != null); + _useSeasonFolders = useSeasonFolders; + notifyListeners(); + } + + String _seriesPath = ''; + String get seriesPath => _seriesPath; + set seriesPath(String seriesPath) { + assert(seriesPath != null); + _seriesPath = seriesPath; + notifyListeners(); + } + + SonarrSeriesType _seriesType; + SonarrSeriesType get seriesType => _seriesType; + set seriesType(SonarrSeriesType seriesType) { + assert(seriesType != null); + _seriesType = seriesType; + notifyListeners(); + } + + SonarrQualityProfile _qualityProfile; + SonarrQualityProfile get qualityProfile => _qualityProfile; + set qualityProfile(SonarrQualityProfile qualityProfile) { + assert(qualityProfile != null); + _qualityProfile = qualityProfile; + notifyListeners(); + } + + SonarrLanguageProfile _languageProfile; + SonarrLanguageProfile get languageProfile => _languageProfile; + set languageProfile(SonarrLanguageProfile languageProfile) { + assert(languageProfile != null); + _languageProfile = languageProfile; + notifyListeners(); + } +} diff --git a/lib/modules/sonarr/modules/series_edit/widgets.dart b/lib/modules/sonarr/modules/series_edit/widgets.dart new file mode 100644 index 00000000..91f82fdb --- /dev/null +++ b/lib/modules/sonarr/modules/series_edit/widgets.dart @@ -0,0 +1,7 @@ +export 'widgets/button_update_series.dart'; +export 'widgets/tile_language_profile.dart'; +export 'widgets/tile_monitored.dart'; +export 'widgets/tile_quality_profile.dart'; +export 'widgets/tile_series_path.dart'; +export 'widgets/tile_series_type.dart'; +export 'widgets/tile_use_season_folders.dart'; diff --git a/lib/modules/sonarr/modules/series_edit/widgets/button_update_series.dart b/lib/modules/sonarr/modules/series_edit/widgets/button_update_series.dart new file mode 100644 index 00000000..353491e2 --- /dev/null +++ b/lib/modules/sonarr/modules/series_edit/widgets/button_update_series.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesEditUpdateSeriesButton extends StatelessWidget { + final SonarrSeries series; + + SonarrSeriesEditUpdateSeriesButton({ + Key key, + @required this.series, + }) : super(key: key); + + @override + Widget build(BuildContext context) => Row( + children: [ + Expanded( + child: Card( + child: InkWell( + child: ListTile( + title: context.watch().state == LunaLoadingState.INACTIVE + ? Text( + 'Update Series', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: Constants.UI_FONT_SIZE_STICKYHEADER, + ), + textAlign: TextAlign.center, + ) + : LSLoader( + color: Colors.white, + size: 20.0, + ), + ), + borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), + onTap: context.watch().state == LunaLoadingState.INACTIVE + ? () async => _onTap(context) + : null, + ), + color: LunaColours.accent, + margin: Constants.UI_CARD_MARGIN, + elevation: Constants.UI_ELEVATION, + shape: LSRoundedShape(), + ), + ), + ], + ); + + Future _onTap(BuildContext context) async { + // Set loading state + SonarrSeriesEditState _editState = context.read(); + _editState.state = LunaLoadingState.ACTIVE; + // Deep copy series, update edits + SonarrSeries _series = series.clone(); + _series.updateEdits(_editState); + // Send to Sonarr + SonarrState _globalState = context.read(); + _globalState.api.series.updateSeries(series: _series) + .then((_) async { + // Update internal series list, show snackbar, pop route + _globalState.resetSeries(); + await _globalState.series.then((_) { + LSSnackBar( + context: context, + title: 'Updated Series', + message: _series.title, + type: SNACKBAR_TYPE.success, + ); + Navigator.of(context).pop(); + }); + }) + .catchError((error, stack) { + // Log error, show error message + LunaLogger.error( + 'SonarrSeriesEditUpdateSeriesButton', + '_onTap', + 'Failed to update series: ${series.id}', + error, + stack, + uploadToSentry: !(error is DioError), + ); + _editState.state = LunaLoadingState.ERROR; + }); + } +} diff --git a/lib/modules/sonarr/modules/series_edit/widgets/tile_language_profile.dart b/lib/modules/sonarr/modules/series_edit/widgets/tile_language_profile.dart new file mode 100644 index 00000000..7b2c8676 --- /dev/null +++ b/lib/modules/sonarr/modules/series_edit/widgets/tile_language_profile.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesEditLanguageProfileTile extends StatelessWidget { + final List profiles; + + SonarrSeriesEditLanguageProfileTile({ + Key key, + @required this.profiles, + }) : super(key: key); + + @override + Widget build(BuildContext context) => LSCardTile( + title: LSTitle(text: 'Language Profile'), + subtitle: LSSubtitle(text: context.watch().languageProfile.name), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: () async => _onTap(context), + ); + + Future _onTap(BuildContext context) async { + List _values = await SonarrDialogs.editLanguageProfiles(context, profiles); + if(_values[0]) context.read().languageProfile = _values[1]; + } +} diff --git a/lib/modules/sonarr/modules/series_edit/widgets/tile_monitored.dart b/lib/modules/sonarr/modules/series_edit/widgets/tile_monitored.dart new file mode 100644 index 00000000..61ec8147 --- /dev/null +++ b/lib/modules/sonarr/modules/series_edit/widgets/tile_monitored.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesEditMonitoredTile extends StatelessWidget { + @override + Widget build(BuildContext context) => LSCardTile( + title: LSTitle(text: 'Monitored'), + subtitle: LSSubtitle(text: 'Monitor series for new releases'), + trailing: Switch( + value: context.watch().monitored, + onChanged: (value) => context.read().monitored = value, + ), + ); +} diff --git a/lib/modules/sonarr/modules/series_edit/widgets/tile_quality_profile.dart b/lib/modules/sonarr/modules/series_edit/widgets/tile_quality_profile.dart new file mode 100644 index 00000000..2eef581b --- /dev/null +++ b/lib/modules/sonarr/modules/series_edit/widgets/tile_quality_profile.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesEditQualityProfileTile extends StatelessWidget { + final List profiles; + + SonarrSeriesEditQualityProfileTile({ + Key key, + @required this.profiles, + }) : super(key: key); + + @override + Widget build(BuildContext context) => LSCardTile( + title: LSTitle(text: 'Quality Profile'), + subtitle: LSSubtitle(text: context.watch().qualityProfile.name), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: () async => _onTap(context), + ); + + Future _onTap(BuildContext context) async { + List _values = await SonarrDialogs.editQualityProfile(context, profiles); + if(_values[0]) context.read().qualityProfile = _values[1]; + } +} diff --git a/lib/modules/sonarr/modules/series_edit/widgets/tile_series_path.dart b/lib/modules/sonarr/modules/series_edit/widgets/tile_series_path.dart new file mode 100644 index 00000000..354f37eb --- /dev/null +++ b/lib/modules/sonarr/modules/series_edit/widgets/tile_series_path.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesEditSeriesPathTile extends StatelessWidget { + @override + Widget build(BuildContext context) => LSCardTile( + title: LSTitle(text: 'Series Path'), + subtitle: LSSubtitle(text: context.watch().seriesPath ?? Constants.TEXT_EMDASH), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: () async => _onTap(context), + ); + + Future _onTap(BuildContext context) async { + List _values = await LunaDialogs.editText( + context, + 'Series Type', + prefill: context.read().seriesPath, + ); + if(_values[0]) context.read().seriesPath = _values[1]; + } +} diff --git a/lib/modules/sonarr/modules/series_edit/widgets/tile_series_type.dart b/lib/modules/sonarr/modules/series_edit/widgets/tile_series_type.dart new file mode 100644 index 00000000..340d1fbf --- /dev/null +++ b/lib/modules/sonarr/modules/series_edit/widgets/tile_series_type.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesEditSeriesTypeTile extends StatelessWidget { + @override + Widget build(BuildContext context) => LSCardTile( + title: LSTitle(text: 'Series Type'), + subtitle: LSSubtitle(text: context.watch().seriesType.value?.lsLanguage_Capitalize() ?? Constants.TEXT_EMDASH), + trailing: LSIconButton(icon: Icons.arrow_forward_ios), + onTap: () async => _onTap(context), + ); + + Future _onTap(BuildContext context) async { + List _values = await SonarrDialogs.editSeriesType(context); + if(_values[0]) context.read().seriesType = _values[1]; + } +} diff --git a/lib/modules/sonarr/modules/series_edit/widgets/tile_use_season_folders.dart b/lib/modules/sonarr/modules/series_edit/widgets/tile_use_season_folders.dart new file mode 100644 index 00000000..39c10cd9 --- /dev/null +++ b/lib/modules/sonarr/modules/series_edit/widgets/tile_use_season_folders.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesEditSeasonFoldersTile extends StatelessWidget { + @override + Widget build(BuildContext context) => LSCardTile( + title: LSTitle(text: 'Use Season Folders'), + subtitle: LSSubtitle(text: 'Sort episodes into season folders'), + trailing: Switch( + value: context.watch().useSeasonFolders, + onChanged: (value) => context.read().useSeasonFolders = value, + ), + ); +} diff --git a/lib/modules/sonarr/modules/series_season_details.dart b/lib/modules/sonarr/modules/series_season_details.dart new file mode 100644 index 00000000..9f0147a0 --- /dev/null +++ b/lib/modules/sonarr/modules/series_season_details.dart @@ -0,0 +1,2 @@ +export 'series_season_details/route.dart'; +export 'series_season_details/widgets.dart'; diff --git a/lib/modules/sonarr/modules/series_season_details/route.dart b/lib/modules/sonarr/modules/series_season_details/route.dart new file mode 100644 index 00000000..3ee489b9 --- /dev/null +++ b/lib/modules/sonarr/modules/series_season_details/route.dart @@ -0,0 +1,157 @@ +import 'package:fluro_fork/fluro_fork.dart'; +import 'package:flutter/material.dart' hide Router; +import 'package:flutter/scheduler.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesSeasonDetailsRouter { + static const String ROUTE_NAME = '/sonarr/series/details/:seriesid/season/:seasonnumber'; + + static Future navigateTo(BuildContext context, { + @required int seriesId, + @required int seasonNumber, + }) async => LunaRouter.router.navigateTo( + context, + route(seriesId: seriesId, seasonNumber: seasonNumber), + ); + + static String route({ + @required int seriesId, + @required int seasonNumber, + }) => ROUTE_NAME + .replaceFirst(':seriesid', seriesId.toString()) + .replaceFirst(':seasonnumber', seasonNumber.toString()); + + static void defineRoutes(Router router) { + router.define( + ROUTE_NAME, + handler: Handler(handlerFunc: (context, params) => _SonarrSeriesSeasonDetailsRoute( + seriesId: int.tryParse(params['seriesid'][0]) ?? -1, + seasonNumber: int.tryParse(params['seasonnumber'][0]) ?? -1, + )), + transitionType: LunaRouter.transitionType, + ); + } +} + +class _SonarrSeriesSeasonDetailsRoute extends StatefulWidget { + final int seriesId; + final int seasonNumber; + + _SonarrSeriesSeasonDetailsRoute({ + Key key, + @required this.seriesId, + @required this.seasonNumber, + }) : super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State<_SonarrSeriesSeasonDetailsRoute> { + final GlobalKey _scaffoldKey = GlobalKey(); + final GlobalKey _refreshKey = GlobalKey(); + + @override + void initState() { + super.initState(); + SchedulerBinding.instance.scheduleFrameCallback((_) { + context.read().selectedEpisodes = []; + _refresh(); + }); + } + + Future _refresh() async { + if(context.read().api != null) + context.read().fetchEpisodes(widget.seriesId); + if(context.read().episodes[widget.seriesId] != null) + await context.read().episodes[widget.seriesId]; + } + + @override + Widget build(BuildContext context) => Scaffold( + key: _scaffoldKey, + appBar: _appBar, + body: _body, + floatingActionButton: _floatingActionButton, + ); + + Widget get _appBar => LunaAppBar( + context: context, + title: 'Season Details', + popUntil: '/sonarr', + ); + + Widget get _floatingActionButton => context.watch().selectedEpisodes.length == 0 + ? null + : LSFloatingActionButtonExtended( + label: context.watch().selectedEpisodes.length == 1 + ? '1 Episode' + : '${context.watch().selectedEpisodes.length} Episodes', + icon: Icons.search, + onPressed: () => _searchSelected(), + ); + + Widget get _body => LSRefreshIndicator( + refreshKey: _refreshKey, + onRefresh: _refresh, + child: FutureBuilder( + future: context.watch().episodes[widget.seriesId], + builder: (context, AsyncSnapshot> snapshot) { + if(snapshot.hasError) return LSErrorMessage(onTapHandler: () => _refresh()); + if(snapshot.hasData) { + if(widget.seasonNumber == -1) return SonarrSeriesSeasonDetailsAllSeasons( + episodes: snapshot.data, + seriesId: widget.seriesId, + ); + List _episodes = snapshot.data.where( + (episode) => episode.seasonNumber == widget.seasonNumber, + ).toList(); + if(_episodes != null && _episodes.length > 0) { + _episodes.sort((a,b) => (b.episodeNumber ?? 0).compareTo(a.episodeNumber ?? 0)); + return SonarrSeriesSeasonDetailsSeason( + episodes: _episodes, + seriesId: widget.seriesId, + seasonNumber: widget.seasonNumber, + ); + } + return _unknown; + } + return LSLoader(); + }, + ), + ); + + Widget get _unknown => LSGenericMessage(text: 'No Episodes Found'); + + Future _searchSelected() async { + if(context.read().api != null) context.read().api.command.episodeSearch( + episodeIds: context.read().selectedEpisodes, + ).then((_) { + LSSnackBar( + context: context, + title: 'Searching for Episodes...', + message: context.read().selectedEpisodes.length == 1 + ? '1 Episode' + : '${context.read().selectedEpisodes.length} Episodes', + type: SNACKBAR_TYPE.success, + ); + context.read().selectedEpisodes = []; + }) + .catchError((error, stack) { + LunaLogger.error( + '', + '_searchSelected', + 'Failed to search for episodes: ${context.read().selectedEpisodes.join(', ')}', + error, + stack, + uploadToSentry: !(error is DioError), + ); + LSSnackBar( + context: context, + title: 'Failed to Search For Episodes', + type: SNACKBAR_TYPE.failure, + ); + }); + } +} diff --git a/lib/modules/sonarr/modules/series_season_details/widgets.dart b/lib/modules/sonarr/modules/series_season_details/widgets.dart new file mode 100644 index 00000000..cc7582be --- /dev/null +++ b/lib/modules/sonarr/modules/series_season_details/widgets.dart @@ -0,0 +1,4 @@ +export 'widgets/all_seasons.dart'; +export 'widgets/season.dart'; +export 'widgets/season_header.dart'; +export 'widgets/tile_episode.dart'; diff --git a/lib/modules/sonarr/modules/series_season_details/widgets/all_seasons.dart b/lib/modules/sonarr/modules/series_season_details/widgets/all_seasons.dart new file mode 100644 index 00000000..08a508fe --- /dev/null +++ b/lib/modules/sonarr/modules/series_season_details/widgets/all_seasons.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesSeasonDetailsAllSeasons extends StatelessWidget { + final int seriesId; + final List episodes; + + SonarrSeriesSeasonDetailsAllSeasons({ + Key key, + @required this.seriesId, + @required this.episodes, + }) : super(key: key); + + @override + Widget build(BuildContext context) => LSListView( + children: _buildSeasons, + ); + + List get _buildSeasons { + // Put episodes into a map with the key being the season number + Map> _episodeMap = {}; + episodes.forEach((episode) { + if(!_episodeMap.containsKey(episode.seasonNumber)) _episodeMap[episode.seasonNumber] = []; + _episodeMap[episode.seasonNumber].add(episode); + }); + // Sort the keys + List _keys = _episodeMap.keys.toList(); + _keys.sort((a,b) => b.compareTo(a)); + // Build each season + List> _episodes = _keys.fold([], (array, season) { + _episodeMap[season].sort((a,b) => b.episodeNumber.compareTo(a.episodeNumber)); + array.add(_season(_episodeMap[season], season)); + return array; + }); + // Return the final list of seasons + return _episodes.expand((e) => e).toList(); + } + + List _season(List episodes, int seasonNumber) => [ + SonarrSeriesSeasonDetailsSeasonHeader( + seriesId: seriesId, + seasonNumber: seasonNumber, + episodes: episodes, + ), + ...List.generate( + episodes.length, + (index) => SonarrSeriesSeasonDetailsEpisodeTile(episode: episodes[index]), + ), + ]; +} diff --git a/lib/modules/sonarr/modules/series_season_details/widgets/season.dart b/lib/modules/sonarr/modules/series_season_details/widgets/season.dart new file mode 100644 index 00000000..0facd6a7 --- /dev/null +++ b/lib/modules/sonarr/modules/series_season_details/widgets/season.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesSeasonDetailsSeason extends StatelessWidget { + final int seasonNumber; + final int seriesId; + final List episodes; + + SonarrSeriesSeasonDetailsSeason({ + Key key, + @required this.seasonNumber, + @required this.seriesId, + @required this.episodes, + }) : super(key: key); + + @override + Widget build(BuildContext context) => LSListView( + children: [ + SonarrSeriesSeasonDetailsSeasonHeader( + seriesId: seriesId, + seasonNumber: seasonNumber, + episodes: episodes, + ), + ...List.generate( + episodes.length, + (index) => SonarrSeriesSeasonDetailsEpisodeTile(episode: episodes[index]), + ), + ], + ); +} diff --git a/lib/modules/sonarr/modules/series_season_details/widgets/season_header.dart b/lib/modules/sonarr/modules/series_season_details/widgets/season_header.dart new file mode 100644 index 00000000..e6b51d81 --- /dev/null +++ b/lib/modules/sonarr/modules/series_season_details/widgets/season_header.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesSeasonDetailsSeasonHeader extends StatelessWidget { + final int seasonNumber; + final int seriesId; + final List episodes; + + SonarrSeriesSeasonDetailsSeasonHeader({ + Key key, + @required this.seriesId, + @required this.seasonNumber, + @required this.episodes, + }) : super(key: key); + + @override + Widget build(BuildContext context) => GestureDetector( + child: LSHeader( + text: seasonNumber == 0 + ? 'Specials' + : 'Season $seasonNumber', + ), + onTap: () async => _onTap(context), + onLongPress: () async => handler(context, seriesId, seasonNumber), + ); + + Future _onTap(BuildContext context) async { + bool _allSelected = true; + for(SonarrEpisode episode in episodes) { + if(!_allSelected) break; + _allSelected = context.read().selectedEpisodes.contains(episode.id); + } + episodes.forEach((episode) => _allSelected + ? context.read().removeSelectedEpisode(episode.id) + : context.read().addSelectedEpisode(episode.id), + ); + } + + static Future handler(BuildContext context, int seriesId, int seasonNumber) async { + List values = await SonarrDialogs.seasonSettings(context, seasonNumber); + if(values[0]) switch(values[1] as SonarrSeasonSettingsType) { + case SonarrSeasonSettingsType.AUTOMATIC_SEARCH: _automaticSearch(context, seriesId, seasonNumber); break; + case SonarrSeasonSettingsType.INTERACTIVE_SEARCH: _interactiveSearch(context, seriesId, seasonNumber); break; + default: LunaLogger.warning('SonarrAppBarSeriesSettingsAction', 'handler', 'Unknown case: ${(values[1] as SonarrSeriesSettingsType)}'); + } + } + + static Future _automaticSearch(BuildContext context, int seriesId, int seasonNumber) async { + List _values = await SonarrDialogs.confirmSeasonSearch(context, seasonNumber); + if(_values[0] && context.read().api != null) context.read().api.command.seasonSearch( + seriesId: seriesId, + seasonNumber: seasonNumber, + ) + .then((_) => LSSnackBar( + context: context, + title: 'Searching for Season...', + message: seasonNumber == 0 + ? 'Specials' + : 'Season $seasonNumber', + type: SNACKBAR_TYPE.success, + )) + .catchError((error, stack) { + LunaLogger.error( + 'SonarrSeriesDetailsSeasonTile', + '_onLongPress', + 'Failed season search: $seriesId, season $seasonNumber', + error, + stack, + uploadToSentry: !(error is DioError), + ); + LSSnackBar( + context: context, + title: 'Failed to Season Search', + type: SNACKBAR_TYPE.failure, + ); + }); + } + + static Future _interactiveSearch( + BuildContext context, + int seriesId, + int seasonNumber, + ) async => SonarrReleasesRouter.navigateTo(context, seriesId: seriesId, seasonNumber: seasonNumber); +} diff --git a/lib/modules/sonarr/modules/series_season_details/widgets/tile_episode.dart b/lib/modules/sonarr/modules/series_season_details/widgets/tile_episode.dart new file mode 100644 index 00000000..62191d80 --- /dev/null +++ b/lib/modules/sonarr/modules/series_season_details/widgets/tile_episode.dart @@ -0,0 +1,339 @@ +import 'package:expandable/expandable.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrSeriesSeasonDetailsEpisodeTile extends StatefulWidget { + final SonarrEpisode episode; + + SonarrSeriesSeasonDetailsEpisodeTile({ + Key key, + @required this.episode, + }) : super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State { + final ExpandableController _expandableController = ExpandableController(); + + @override + Widget build(BuildContext context) => LSExpandable( + controller: _expandableController, + collapsed: _collapsed, + expanded: _expanded, + ); + + Widget get _expanded => LSCard( + child: InkWell( + child: Row( + children: [ + Expanded( + child: Padding( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LSTitle(text: widget.episode.title, softWrap: true, maxLines: 12), + Padding( + child: Wrap( + direction: Axis.horizontal, + runSpacing: 10.0, + children: [ + if(!widget.episode.monitored) LSTextHighlighted( + text: 'Unmonitored', + bgColor: LunaColours.red, + ), + if(widget.episode.hasFile) LSTextHighlighted( + bgColor: widget.episode.episodeFile.qualityCutoffNotMet + ? LunaColours.orange + : LunaColours.accent, + text: [ + widget.episode.episodeFile.quality.quality.name, + ' ${Constants.TEXT_EMDASH} ', + widget.episode.episodeFile.size.lsBytes_BytesToString(), + ].join(), + ), + if(!widget.episode.hasFile && (widget.episode?.airDateUtc?.toLocal()?.isAfter(DateTime.now()) ?? true)) LSTextHighlighted( + bgColor: LunaColours.blue, + text: 'Unaired', + ), + if(!widget.episode.hasFile && (widget.episode?.airDateUtc?.toLocal()?.isBefore(DateTime.now()) ?? false)) LSTextHighlighted( + bgColor: LunaColours.red, + text: 'Missing', + ), + ], + ), + padding: EdgeInsets.only(top: 8.0, bottom: 2.0), + ), + Padding( + child: RichText( + text: TextSpan( + style: TextStyle( + color: Colors.white70, + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + ), + children: [ + TextSpan( + text: widget.episode.seasonNumber == 0 + ? 'Specials / Episode ${widget.episode.episodeNumber}\n' + : 'Season ${widget.episode.seasonNumber} / Episode ${widget.episode.episodeNumber}\n', + style: TextStyle( + color: LunaColours.accent, + fontWeight: FontWeight.w600, + fontSize: Constants.UI_FONT_SIZE_STICKYHEADER, + ), + ), + TextSpan( + text: widget.episode.airDateUtc == null + ? 'Unknown Date' + : DateFormat.yMMMMd().format(widget.episode.airDateUtc.toLocal()), + style: TextStyle( + color: Colors.white, + ), + ), + TextSpan(text: '\n\n'), + TextSpan( + text: widget.episode.overview == null || widget.episode.overview.isEmpty + ? 'No overview is available.' + : widget.episode.overview, + style: TextStyle( + fontStyle: FontStyle.italic, + ), + ) + ], + ), + ), + padding: EdgeInsets.only(top: 6.0, bottom: 10.0), + ), + Padding( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: LSButtonSlim( + text: 'Automatic', + onTap: _automaticSearch, + margin: EdgeInsets.only(right: 6.0), + ), + ), + Expanded( + child: LSButtonSlim( + text: 'Interactive', + backgroundColor: LunaColours.orange, + onTap: _interactiveSearch, + margin: EdgeInsets.only(left: 6.0), + ), + ), + ], + ), + padding: EdgeInsets.only(bottom: 2.0), + ), + ], + ), + padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 10.0), + ), + ) + ], + ), + borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), + onTap: () => _expandableController.toggle(), + onLongPress: () => _handleEpisodeSettings(), + ), + color: context.watch().selectedEpisodes.contains(widget.episode.id) + ? LunaColours.accent.withOpacity(0.15) + : LunaSeaDatabaseValue.THEME_AMOLED.data ? Colors.black : LunaColours.secondary, + ); + + + Widget get _collapsed => LSCardTile( + title: LSTitle(text: widget.episode.title, darken: !widget.episode.monitored), + subtitle: _subtitle, + leading: _leading, + trailing: _trailing, + padContent: true, + onTap: () => _expandableController.toggle(), + onLongPress: () async => _handleEpisodeSettings(), + color: context.watch().selectedEpisodes.contains(widget.episode.id) + ? LunaColours.accent.withOpacity(0.15) + : LunaSeaDatabaseValue.THEME_AMOLED.data ? Colors.black : LunaColours.secondary, + ); + + Widget get _subtitle => RichText( + text: TextSpan( + style: TextStyle( + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + color: widget.episode.monitored ? Colors.white70 : Colors.white30, + ), + children: [ + TextSpan( + text: widget.episode.airDateUtc == null + ? 'Unknown Date' + : DateFormat.yMMMMd().format(widget.episode.airDateUtc.toLocal()), + ), + TextSpan(text: '\n'), + if(widget.episode.hasFile) TextSpan( + style: TextStyle( + fontWeight: FontWeight.w600, + color: widget.episode.episodeFile.qualityCutoffNotMet + ? widget.episode.monitored ? LunaColours.orange : LunaColours.orange.withOpacity(0.30) + : widget.episode.monitored ? LunaColours.accent : LunaColours.accent.withOpacity(0.30), + ), + children: [ + TextSpan(text: widget.episode.episodeFile.quality.quality.name), + TextSpan(text: ' ${Constants.TEXT_EMDASH} '), + TextSpan(text: widget.episode.episodeFile.size.lsBytes_BytesToString()), + ], + ), + if(!widget.episode.hasFile && (widget.episode?.airDateUtc?.toLocal()?.isAfter(DateTime.now()) ?? true)) TextSpan( + style: TextStyle( + fontWeight: FontWeight.w600, + color: widget.episode.monitored ? LunaColours.blue : LunaColours.blue.withOpacity(0.30), + ), + text: 'Unaired', + ), + if(!widget.episode.hasFile && (widget.episode?.airDateUtc?.toLocal()?.isBefore(DateTime.now()) ?? false)) TextSpan( + style: TextStyle( + fontWeight: FontWeight.w600, + color: widget.episode.monitored ? LunaColours.red : LunaColours.red.withOpacity(0.30), + ), + text: 'Missing', + ), + ], + ), + overflow: TextOverflow.fade, + softWrap: false, + maxLines: 2, + ); + + Widget get _leading => IconButton( + icon: context.watch().selectedEpisodes.contains(widget.episode.id) + ? LSIcon(icon: Icons.check) + : Text( + '${widget.episode.episodeNumber}', + textAlign: TextAlign.center, + style: TextStyle( + color: widget.episode.monitored + ? Colors.white + : Colors.white30, + fontWeight: FontWeight.w600, + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + ), + ), + onPressed: () => context.read().toggleSelectedEpisode(widget.episode.id), + ); + + Widget get _trailing => LSIconButton( + icon: Icons.search, + color: widget.episode.monitored + ? Colors.white + : Colors.white30, + onPressed: _automaticSearch, + onLongPress: _interactiveSearch, + ); + + Future _automaticSearch() async { + Provider.of(context, listen: false).api.command.episodeSearch(episodeIds: [widget.episode.id]) + .then((_) => LSSnackBar( + context: context, + title: 'Searching for Episode...', + message: widget.episode.title, + type: SNACKBAR_TYPE.success, + )) + .catchError((error, stack) { + LunaLogger.error( + 'SonarrMissingTile', + '_trailingOnPressed', + 'Failed to search for episode: ${widget.episode.id}', + error, + stack, + uploadToSentry: !(error is DioError), + ); + LSSnackBar( + context: context, + title: 'Failed to Search', + type: SNACKBAR_TYPE.failure, + ); + }); + } + + Future _interactiveSearch() async => SonarrReleasesRouter.navigateTo( + context, + episodeId: widget.episode.id, + ); + + Future _handleEpisodeSettings() async { + List _values = await SonarrDialogs.episodeSettings(context, widget.episode); + if(_values[0]) switch((_values[1] as SonarrEpisodeSettingsType)) { + case SonarrEpisodeSettingsType.MONITORED: _handleToggleMonitored(); break; + case SonarrEpisodeSettingsType.AUTOMATIC_SEARCH: _automaticSearch(); break; + case SonarrEpisodeSettingsType.INTERACTIVE_SEARCH: _interactiveSearch(); break; + case SonarrEpisodeSettingsType.DELETE_FILE: _handleDeleteFile(); break; + } + } + + Future _handleDeleteFile() async { + List _values = await SonarrDialogs.confirmDeleteEpisodeFile(context); + if(_values[0] && context.read().api != null) context.read().api.episodeFile.deleteEpisodeFile( + episodeFileId: widget.episode.episodeFileId, + ).then((_) { + setState(() => widget.episode.hasFile = false); + LSSnackBar( + context: context, + title: 'Deleted Episode File', + message: widget.episode.title, + type: SNACKBAR_TYPE.success, + ); + }) + .catchError((error, stack) { + LunaLogger.error( + '_handleDeleteFile', + '_handleToggleMonitored', + 'Failed to delete episode file: ${widget.episode.episodeFileId}', + error, + stack, + uploadToSentry: !(error is DioError), + ); + LSSnackBar( + context: context, + title: 'Failed to Delete Episode File', + type: SNACKBAR_TYPE.failure, + ); + }); + } + + Future _handleToggleMonitored() async { + SonarrEpisode _episode = widget.episode.clone(); + _episode.monitored = !_episode.monitored; + if(context.read().api != null) context.read().api.episode.updateEpisode(episode: _episode) + .then((_) { + setState(() => widget.episode.monitored = _episode.monitored); + LSSnackBar( + context: context, + title: _episode.monitored + ? 'Monitoring' + : 'No Longer Monitoring', + message: _episode.title, + type: SNACKBAR_TYPE.success, + ); + }) + .catchError((error, stack) { + LunaLogger.error( + 'SonarrSeriesSeasonDetailsEpisodeTile', + '_handleToggleMonitored', + 'Failed to set episode monitored state: ${_episode.id}, ${_episode.monitored}', + error, + stack, + uploadToSentry: !(error is DioError), + ); + LSSnackBar( + context: context, + title: _episode.monitored + ? 'Failed to Start Monitoring' + : 'Failed to Stop Monitoring', + type: SNACKBAR_TYPE.failure, + ); + }); + } +} diff --git a/lib/modules/sonarr/modules/sonarr.dart b/lib/modules/sonarr/modules/sonarr.dart new file mode 100644 index 00000000..90d63916 --- /dev/null +++ b/lib/modules/sonarr/modules/sonarr.dart @@ -0,0 +1,2 @@ +export 'sonarr/route.dart'; +export 'sonarr/widgets.dart'; diff --git a/lib/modules/sonarr/modules/sonarr/route.dart b/lib/modules/sonarr/modules/sonarr/route.dart new file mode 100644 index 00000000..fddf44e9 --- /dev/null +++ b/lib/modules/sonarr/modules/sonarr/route.dart @@ -0,0 +1,96 @@ +import 'package:fluro_fork/fluro_fork.dart'; +import 'package:flutter/material.dart' hide Router; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrHomeRouter { + static const ROUTE_NAME = '/sonarr'; + + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) { + router.define( + ROUTE_NAME, + handler: Handler(handlerFunc: (context, params) => _SonarrHomeRoute()), + transitionType: LunaRouter.transitionType, + ); + } + + SonarrHomeRouter._(); +} + +class _SonarrHomeRoute extends StatefulWidget { + @override + State<_SonarrHomeRoute> createState() => _State(); +} + +class _State extends State<_SonarrHomeRoute> { + final GlobalKey _scaffoldKey = GlobalKey(); + final ScrollController _catalogueScrollController = ScrollController(); + PageController _pageController; + + @override + void initState() { + super.initState(); + _pageController = PageController(initialPage: SonarrDatabaseValue.NAVIGATION_INDEX.data); + } + + @override + Widget build(BuildContext context) => WillPopScope( + onWillPop: _onWillPop, + child: ValueListenableBuilder( + valueListenable: Database.lunaSeaBox.listenable(keys: [ LunaSeaDatabaseValue.ENABLED_PROFILE.key ]), + builder: (context, box, _) => Scaffold( + key: _scaffoldKey, + drawer: _drawer, + appBar: _appBar, + bottomNavigationBar: _bottomNavigationBar, + body: _body, + ), + ), + ); + + Future _onWillPop() async { + if(_scaffoldKey.currentState.isDrawerOpen) return true; + _scaffoldKey.currentState.openDrawer(); + return false; + } + + Widget get _drawer => LSDrawer(page: 'sonarr'); + + Widget get _bottomNavigationBar => SonarrNavigationBar(pageController: _pageController); + + List get _tabs => [ + SonarrSeriesRoute(scrollController: _catalogueScrollController), + SonarrUpcomingRoute(), + SonarrMissingRoute(), + SonarrHistoryRoute(), + ]; + + Widget get _body => Selector( + selector: (_, state) => state.enabled, + builder: (context, enabled, _) => PageView( + controller: _pageController, + children: enabled ? _tabs : List.generate(_tabs.length, (_) => LSNotEnabled('Sonarr')), + ), + ); + + Widget get _appBar => SonarrAppBar( + context: context, + profiles: Database.profilesBox.keys.fold([], (value, element) { + if((Database.profilesBox.get(element) as ProfileHiveObject)?.sonarrEnabled ?? false) value.add(element); + return value; + }), + actions: Provider.of(context).enabled + ? [ + SonarrAppBarAddSeriesAction(), + SonarrAppBarGlobalSettingsAction(), + ] + : null, + ); +} diff --git a/lib/modules/sonarr/modules/sonarr/widgets.dart b/lib/modules/sonarr/modules/sonarr/widgets.dart new file mode 100644 index 00000000..359ff914 --- /dev/null +++ b/lib/modules/sonarr/modules/sonarr/widgets.dart @@ -0,0 +1,4 @@ +export 'widgets/appbar.dart'; +export 'widgets/appbar_add_series_action.dart'; +export 'widgets/appbar_global_settings_action.dart'; +export 'widgets/navigation_bar.dart'; diff --git a/lib/modules/sonarr/modules/sonarr/widgets/appbar.dart b/lib/modules/sonarr/modules/sonarr/widgets/appbar.dart new file mode 100644 index 00000000..98c43bba --- /dev/null +++ b/lib/modules/sonarr/modules/sonarr/widgets/appbar.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; + +// ignore: non_constant_identifier_names +Widget SonarrAppBar({ + @required BuildContext context, + @required List profiles, + @required List actions, +}) => profiles != null && profiles.length < 2 + ? LunaAppBar( + context: context, + title: 'Sonarr', + actions: actions, + popUntil: null, + hideLeading: true, + ) + : AppBar( + title: PopupMenuButton( + shape: LunaSeaDatabaseValue.THEME_AMOLED.data && LunaSeaDatabaseValue.THEME_AMOLED_BORDER.data + ? LSRoundedShapeWithBorder() + : LSRoundedShape(), + child: Wrap( + direction: Axis.horizontal, + children: [ + Text( + 'Sonarr', + style: TextStyle( + fontSize: Constants.UI_FONT_SIZE_HEADER, + ), + ), + LSIcon( + icon: Icons.arrow_drop_down, + ), + ], + ), + onSelected: (result) => LunaProfile.changeProfile(context, result), + itemBuilder: (context) { + return >[for(String profile in profiles) PopupMenuItem( + value: profile, + child: Text( + profile, + style: TextStyle( + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + ), + ), + )]; + }, + ), + centerTitle: false, + elevation: 0, + actions: actions, + ); \ No newline at end of file diff --git a/lib/modules/sonarr/modules/sonarr/widgets/appbar_add_series_action.dart b/lib/modules/sonarr/modules/sonarr/widgets/appbar_add_series_action.dart new file mode 100644 index 00000000..21df7214 --- /dev/null +++ b/lib/modules/sonarr/modules/sonarr/widgets/appbar_add_series_action.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrAppBarAddSeriesAction extends StatelessWidget { + @override + Widget build(BuildContext context) => LSIconButton( + icon: Icons.add, + onPressed: () async => _onPressed(context), + ); + + Future _onPressed(BuildContext context) async => SonarrSeriesAddRouter.navigateTo(context); +} diff --git a/lib/modules/sonarr/modules/sonarr/widgets/appbar_global_settings_action.dart b/lib/modules/sonarr/modules/sonarr/widgets/appbar_global_settings_action.dart new file mode 100644 index 00000000..7020ad4f --- /dev/null +++ b/lib/modules/sonarr/modules/sonarr/widgets/appbar_global_settings_action.dart @@ -0,0 +1,153 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrAppBarGlobalSettingsAction extends StatelessWidget { + @override + Widget build(BuildContext context) => LSIconButton( + icon: Icons.more_vert, + onPressed: () async => _handler(context), + ); + + Future _handler(BuildContext context) async { + List values = await SonarrDialogs.globalSettings(context); + if(values[0]) switch(values[1] as SonarrGlobalSettingsType) { + case SonarrGlobalSettingsType.WEB_GUI: _webGUI(context); break; + case SonarrGlobalSettingsType.VIEW_QUEUE: _viewQueue(context); break; + case SonarrGlobalSettingsType.MANAGE_TAGS: _manageTags(context); break; + case SonarrGlobalSettingsType.UPDATE_LIBRARY: _updateLibrary(context); break; + case SonarrGlobalSettingsType.RUN_RSS_SYNC: _runRSSSync(context); break; + case SonarrGlobalSettingsType.SEARCH_ALL_MISSING: _searchAllMissing(context); break; + case SonarrGlobalSettingsType.BACKUP_DATABASE: _backupDatabase(context); break; + } + } + + Future _webGUI(BuildContext context) async => Provider.of(context, listen: false).host.lsLinks_OpenLink(); + + Future _viewQueue(BuildContext context) async => LSSnackBar( + context: context, + title: 'Coming Soon!', + message: 'This feature has not yet been implemented', + type: SNACKBAR_TYPE.info, + ); + //Future _viewQueue(BuildContext context) async => SonarrQueueRouter.navigateTo(context); + + Future _manageTags(BuildContext context) async => LSSnackBar( + context: context, + title: 'Coming Soon!', + message: 'This feature has not yet been implemented', + type: SNACKBAR_TYPE.info, + ); + //Future _manageTags(BuildContext context) async => SonarrTagsRouter.navigateTo(context); + + Future _updateLibrary(BuildContext context) async { + Sonarr _sonarr = Provider.of(context, listen: false).api; + if(_sonarr != null) _sonarr.command.refreshSeries() + .then((_) { + LSSnackBar( + context: context, + title: 'Updating Library${Constants.TEXT_ELLIPSIS}', + message: 'Updating library in the background', + ); + }) + .catchError((error, stack) { + LunaLogger.error( + 'SonarrAppBarGlobalSettingsAction', + '_updateLibrary', + 'Unable to update library', + error, + stack, + uploadToSentry: !(error is DioError), + ); + LSSnackBar( + context: context, + title: 'Failed to Update Library', + type: SNACKBAR_TYPE.failure, + ); + }); + } + + Future _runRSSSync(BuildContext context) async { + Sonarr _sonarr = Provider.of(context, listen: false).api; + if(_sonarr != null) _sonarr.command.rssSync() + .then((_) { + LSSnackBar( + context: context, + title: 'Running RSS Sync${Constants.TEXT_ELLIPSIS}', + message: 'Running RSS sync in the background', + ); + }) + .catchError((error, stack) { + LunaLogger.error( + 'SonarrAppBarGlobalSettingsAction', + '_runRSSSync', + 'Unable to run RSS sync', + error, + stack, + uploadToSentry: !(error is DioError), + ); + LSSnackBar( + context: context, + title: 'Failed to Run RSS Sync', + type: SNACKBAR_TYPE.failure, + ); + }); + } + + Future _searchAllMissing(BuildContext context) async { + Sonarr _sonarr = Provider.of(context, listen: false).api; + if(_sonarr != null) { + List _values = await SonarrDialogs.searchAllMissingEpisodes(context); + if(_values[0]) _sonarr.command.missingEpisodeSearch() + .then((_) { + LSSnackBar( + context: context, + title: 'Searching${Constants.TEXT_ELLIPSIS}', + message: 'Searching for all missing episodes', + ); + }) + .catchError((error, stack) { + LunaLogger.error( + 'SonarrAppBarGlobalSettingsAction', + '_searchAllMissing', + 'Unable to search for all missing episodes', + error, + stack, + uploadToSentry: !(error is DioError), + ); + LSSnackBar( + context: context, + title: 'Failed to Search', + type: SNACKBAR_TYPE.failure, + ); + }); + } + } + + Future _backupDatabase(BuildContext context) async { + Sonarr _sonarr = Provider.of(context, listen: false).api; + if(_sonarr != null) _sonarr.command.backup() + .then((_) { + LSSnackBar( + context: context, + title: 'Backing Up Database${Constants.TEXT_ELLIPSIS}', + message: 'Backing up the database in the background', + ); + }) + .catchError((error, stack) { + LunaLogger.error( + 'SonarrAppBarGlobalSettingsAction', + '_backupDatabase', + 'Unable to backup database', + error, + stack, + uploadToSentry: !(error is DioError), + ); + LSSnackBar( + context: context, + title: 'Failed to Backup Database', + type: SNACKBAR_TYPE.failure, + ); + }); + } +} diff --git a/lib/modules/sonarr/widgets/navigation_bar.dart b/lib/modules/sonarr/modules/sonarr/widgets/navigation_bar.dart similarity index 56% rename from lib/modules/sonarr/widgets/navigation_bar.dart rename to lib/modules/sonarr/modules/sonarr/widgets/navigation_bar.dart index f19a58eb..90b9d58e 100644 --- a/lib/modules/sonarr/widgets/navigation_bar.dart +++ b/lib/modules/sonarr/modules/sonarr/widgets/navigation_bar.dart @@ -1,16 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/sonarr.dart'; class SonarrNavigationBar extends StatefulWidget { - static const List titles = [ - 'Catalogue', - 'Upcoming', - 'Missing', - 'History', - ]; - static const List icons = [ CustomIcons.television, CustomIcons.upcoming, @@ -18,6 +10,13 @@ class SonarrNavigationBar extends StatefulWidget { CustomIcons.history, ]; + static const List titles = [ + 'Series', + 'Upcoming', + 'Missing', + 'History', + ]; + final PageController pageController; SonarrNavigationBar({ @@ -30,29 +29,38 @@ class SonarrNavigationBar extends StatefulWidget { } class _State extends State { + int _index = SonarrDatabaseValue.NAVIGATION_INDEX.data; + + @override void initState() { super.initState(); - SchedulerBinding.instance.scheduleFrameCallback((_) { - Provider.of(context, listen: false).navigationIndex = SonarrDatabaseValue.NAVIGATION_INDEX.data; - }); + widget.pageController?.addListener(_pageControllerListener); } @override - Widget build(BuildContext context) => Selector( - selector: (_, model) => model.navigationIndex, - builder: (context, index, _) => LSBottomNavigationBar( - index: index, - icons: SonarrNavigationBar.icons, - titles: SonarrNavigationBar.titles, - onTap: _navOnTap, - ), + void dispose() { + widget.pageController?.removeListener(_pageControllerListener); + super.dispose(); + } + + void _pageControllerListener() { + if(widget.pageController.page.round() == _index) return; + setState(() => _index = widget.pageController.page.round()); + } + + @override + Widget build(BuildContext context) => LSBottomNavigationBar( + index: _index, + onTap: _navOnTap, + icons: SonarrNavigationBar.icons, + titles: SonarrNavigationBar.titles, ); Future _navOnTap(int index) async { - await widget.pageController.animateToPage( + if(widget.pageController.hasClients) widget.pageController.animateToPage( index, duration: Duration(milliseconds: Constants.UI_NAVIGATION_SPEED), curve: Curves.easeOutSine, - ).then((_) => Provider.of(context, listen: false).navigationIndex = index); + ); } -} \ No newline at end of file +} diff --git a/lib/modules/sonarr/modules/tags.dart b/lib/modules/sonarr/modules/tags.dart new file mode 100644 index 00000000..3b3bba4d --- /dev/null +++ b/lib/modules/sonarr/modules/tags.dart @@ -0,0 +1,2 @@ +export 'tags/route.dart'; +export 'tags/widgets.dart'; diff --git a/lib/modules/sonarr/modules/tags/route.dart b/lib/modules/sonarr/modules/tags/route.dart new file mode 100644 index 00000000..73ac8c57 --- /dev/null +++ b/lib/modules/sonarr/modules/tags/route.dart @@ -0,0 +1,99 @@ +import 'package:fluro_fork/fluro_fork.dart'; +import 'package:flutter/material.dart' hide Router; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrTagsRouter { + static const String ROUTE_NAME = '/sonarr/tags/list'; + + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( + context, + route(), + ); + + static String route() => ROUTE_NAME; + + static void defineRoutes(Router router) { + router.define( + ROUTE_NAME, + handler: Handler(handlerFunc: (context, params) => _SonarrTagsRoute()), + transitionType: LunaRouter.transitionType, + ); + } +} + +class _SonarrTagsRoute extends StatefulWidget { + @override + State createState() => _State(); +} + +class _State extends State<_SonarrTagsRoute> { + final GlobalKey _scaffoldKey = GlobalKey(); + final GlobalKey _refreshKey = GlobalKey(); + + @override + void initState() { + super.initState(); + } + + Future _refresh() async { + context.read().resetTags(); + await context.read().tags; + } + + @override + Widget build(BuildContext context) => Scaffold( + key: _scaffoldKey, + appBar: _appBar, + body: _body, + ); + + Widget get _appBar => LunaAppBar( + context: context, + title: 'Tags', + popUntil: '/sonarr', + actions: [ + SonarrTagsAppBarActionAddTag(), + ], + ); + + Widget get _body => LSRefreshIndicator( + refreshKey: _refreshKey, + onRefresh: _refresh, + child: FutureBuilder( + future: context.watch().tags, + builder: (context, AsyncSnapshot> snapshot) { + if(snapshot.hasError) { + if(snapshot.connectionState != ConnectionState.waiting) { + LunaLogger.error( + '_SonarrTagsRoute', + '_body', + 'Unable to fetch Sonarr tags', + snapshot.error, + null, + uploadToSentry: !(snapshot.error is DioError), + ); + } + return LSErrorMessage(onTapHandler: () async => _refreshKey.currentState.show()); + } + if(snapshot.hasData) return snapshot.data.length == 0 + ? _noTags + : _tags(snapshot.data); + return LSLoader(); + }, + ), + ); + + Widget get _noTags => LSGenericMessage( + text: 'No Tags Found', + showButton: true, + onTapHandler: () async => _refreshKey.currentState.show(), + ); + + Widget _tags(List tags) => LSListView( + children: List.generate( + tags.length, + (index) => LSCardTile(title: LSTitle(text: tags[index].label)), + ), + ); +} diff --git a/lib/modules/sonarr/modules/tags/widgets.dart b/lib/modules/sonarr/modules/tags/widgets.dart new file mode 100644 index 00000000..bad9a0de --- /dev/null +++ b/lib/modules/sonarr/modules/tags/widgets.dart @@ -0,0 +1 @@ +export 'widgets/appbar_action_add_tag.dart'; diff --git a/lib/modules/sonarr/modules/tags/widgets/appbar_action_add_tag.dart b/lib/modules/sonarr/modules/tags/widgets/appbar_action_add_tag.dart new file mode 100644 index 00000000..19181b5e --- /dev/null +++ b/lib/modules/sonarr/modules/tags/widgets/appbar_action_add_tag.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; + +class SonarrTagsAppBarActionAddTag extends StatelessWidget { + @override + Widget build(BuildContext context) => LSIconButton( + icon: Icons.add, + onPressed: () async => _onPressed(context), + ); + + Future _onPressed(BuildContext context) async => LSSnackBar( + context: context, + title: 'Coming Soon!', + message: 'This feature has not yet been implemented', + type: SNACKBAR_TYPE.info, + ); +} diff --git a/lib/modules/sonarr/modules/upcoming.dart b/lib/modules/sonarr/modules/upcoming.dart new file mode 100644 index 00000000..be57f945 --- /dev/null +++ b/lib/modules/sonarr/modules/upcoming.dart @@ -0,0 +1,2 @@ +export 'upcoming/route.dart'; +export 'upcoming/widgets.dart'; diff --git a/lib/modules/sonarr/modules/upcoming/route.dart b/lib/modules/sonarr/modules/upcoming/route.dart new file mode 100644 index 00000000..dcb159c5 --- /dev/null +++ b/lib/modules/sonarr/modules/upcoming/route.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:intl/intl.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrUpcomingRoute extends StatefulWidget { + @override + State createState() => _State(); +} + +class _State extends State with AutomaticKeepAliveClientMixin { + final GlobalKey _scaffoldKey = GlobalKey(); + final GlobalKey _refreshKey = GlobalKey(); + + @override + bool get wantKeepAlive => true; + + @override + void initState() { + super.initState(); + SchedulerBinding.instance.scheduleFrameCallback((_) => _refresh()); + } + + Future _refresh() async { + context.read().resetUpcoming(); + await context.read().upcoming; + } + + @override + Widget build(BuildContext context) { + super.build(context); + return Scaffold( + key: _scaffoldKey, + body: _body, + ); + } + + Widget get _body => LSRefreshIndicator( + refreshKey: _refreshKey, + onRefresh: _refresh, + child: Selector>>( + selector: (_, state) => state.upcoming, + builder: (context, future, _) => FutureBuilder( + future: future, + builder: (context, AsyncSnapshot> snapshot) { + if(snapshot.hasError) { + if(snapshot.connectionState != ConnectionState.waiting) { + LunaLogger.error( + '_SonarrUpcomingRoute', + '_body', + 'Unable to fetch Sonarr upcoming episodes', + snapshot.error, + null, + uploadToSentry: !(snapshot.error is DioError), + ); + } + return LSErrorMessage(onTapHandler: () async => _refreshKey.currentState.show()); + } + if(snapshot.hasData) { + return snapshot.data.length == 0 + ? _noEpisodes() + : _episodes(snapshot.data); + } + return LSLoader(); + }, + ), + ), + ); + + Widget _noEpisodes() => LSGenericMessage( + text: 'No Episodes Found', + showButton: true, + buttonText: 'Refresh', + onTapHandler: () async => _refreshKey.currentState.show(), + ); + + Widget _episodes(List upcoming) { + // Split episodes into days into a map + Map> _episodeMap = upcoming.fold({}, (map, entry) { + if(entry.airDateUtc == null) return map; + String _date = DateFormat('y-MM-dd').format(entry.airDateUtc.toLocal()); + if(!map.containsKey(_date)) map[_date] = { + 'date': DateFormat('EEEE / MMMM dd, y').format(entry.airDateUtc.toLocal()), + 'entries': [], + }; + (map[_date]['entries'] as List).add(entry); + return map; + }); + // Build the widgets + List> _episodeWidgets = []; + _episodeMap.keys.toList()..sort()..forEach((key) => { + _episodeWidgets.add(_buildDay( + (_episodeMap[key]['date'] as String), + (_episodeMap[key]['entries'] as List).cast(), + )), + }); + // Return the list + return LSListView( + children: _episodeWidgets.expand((e) => e).toList(), + ); + } + + List _buildDay(String date, List upcoming) => [ + LSHeader(text: date), + ...List.generate( + upcoming.length, + (index) => SonarrUpcomingTile(record: upcoming[index]), + ), + ]; +} \ No newline at end of file diff --git a/lib/modules/sonarr/modules/upcoming/widgets.dart b/lib/modules/sonarr/modules/upcoming/widgets.dart new file mode 100644 index 00000000..e1c776b4 --- /dev/null +++ b/lib/modules/sonarr/modules/upcoming/widgets.dart @@ -0,0 +1 @@ +export 'widgets/upcoming_tile.dart'; diff --git a/lib/modules/sonarr/modules/upcoming/widgets/upcoming_tile.dart b/lib/modules/sonarr/modules/upcoming/widgets/upcoming_tile.dart new file mode 100644 index 00000000..32b13b7e --- /dev/null +++ b/lib/modules/sonarr/modules/upcoming/widgets/upcoming_tile.dart @@ -0,0 +1,198 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/sonarr.dart'; + +class SonarrUpcomingTile extends StatefulWidget { + final SonarrCalendar record; + + SonarrUpcomingTile({ + Key key, + @required this.record, + }) : super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State { + final double _height = 90.0; + final double _width = 60.0; + final double _padding = 8.0; + + @override + Widget build(BuildContext context) => Selector>( + selector: (_, state) => state.missing, + builder: (context, series, _) => LSCard( + child: InkWell( + child: Row( + children: [ + _poster, + Expanded(child: _information), + _trailing, + ], + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + ), + borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), + onTap: _tileOnTap, + onLongPress: _tileOnLongPress, + ), + decoration: LSCardBackground( + uri: Provider.of(context, listen: false).getBannerURL(widget.record.seriesId), + headers: Provider.of(context, listen: false).headers, + ), + ), + ); + + Widget get _poster => LSNetworkImage( + url: Provider.of(context, listen: false).getPosterURL(widget.record.seriesId), + placeholder: 'assets/images/sonarr/noseriesposter.png', + height: _height, + width: _width, + headers: Provider.of(context, listen: false).headers.cast(), + ); + + Widget get _information => Padding( + child: Container( + child: Column( + children: [ + LSTitle(text: widget.record.series.title, darken: !widget.record.monitored, maxLines: 1), + _subtitleOne, + _subtitleTwo, + _subtitleThree, + ], + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + ), + height: (_height-(_padding*2)), + ), + padding: EdgeInsets.all(_padding), + ); + + Widget get _trailing => Container( + child: Padding( + child: InkWell( + child: IconButton( + icon: Text( + widget.record.lunaAirTime, + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: Constants.UI_FONT_SIZE_SUBHEADER-2.0, + ), + ), + onPressed: _trailingOnPressed, + ), + onLongPress: _trailingOnLongPress, + ), + padding: EdgeInsets.only(right: 12.0), + ), + height: _height, + ); + + Widget get _subtitleOne => RichText( + text: TextSpan( + style: TextStyle( + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + color: widget.record.monitored ? Colors.white70 : Colors.white30, + ), + children: [ + TextSpan(text: widget.record.seasonNumber == 0 ? 'Specials ' : 'Season ${widget.record.seasonNumber} '), + TextSpan(text: Constants.TEXT_EMDASH), + TextSpan(text: ' Episode ${widget.record.episodeNumber}'), + ], + ), + overflow: TextOverflow.fade, + softWrap: false, + maxLines: 1, + ); + + Widget get _subtitleTwo => RichText( + text: TextSpan( + style: TextStyle( + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + color: widget.record.monitored ? Colors.white70 : Colors.white30, + ), + children: [ + TextSpan( + style: TextStyle( + fontStyle: FontStyle.italic, + ), + text: widget.record.title ?? 'Unknown Title', + ), + ], + ), + overflow: TextOverflow.fade, + softWrap: false, + maxLines: 1, + ); + + Widget get _subtitleThree => RichText( + text: TextSpan( + style: TextStyle( + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + fontWeight: FontWeight.w600, + ), + children: [ + if(!widget.record.hasFile) TextSpan( + style: TextStyle( + color: LunaColours.red, + ), + text: 'Not Downloaded' + ), + if(widget.record.hasFile) TextSpan( + style: TextStyle( + color: LunaColours.accent, + ), + text: 'Downloaded (${widget?.record?.episodeFile?.quality?.quality?.name ?? 'Unknown'})', + ), + ], + ), + overflow: TextOverflow.fade, + softWrap: false, + maxLines: 1, + ); + + Future _tileOnTap() async => SonarrSeriesSeasonDetailsRouter.navigateTo( + context, + seriesId: widget.record.seriesId, + seasonNumber: widget.record.seasonNumber, + ); + + Future _tileOnLongPress() async => SonarrSeriesDetailsRouter.navigateTo( + context, + seriesId: widget.record.seriesId, + ); + + Future _trailingOnPressed() async { + Provider.of(context, listen: false).api.command.episodeSearch(episodeIds: [widget.record.id]) + .then((_) => LSSnackBar( + context: context, + title: 'Searching for Episode...', + message: widget.record.title, + type: SNACKBAR_TYPE.success, + )) + .catchError((error, stack) { + LunaLogger.error( + 'SonarrUpcomingTile', + '_trailingOnPressed', + 'Failed to search for episode: ${widget.record.id}', + error, + stack, + uploadToSentry: !(error is DioError), + ); + LSSnackBar( + context: context, + title: 'Failed to Search', + type: SNACKBAR_TYPE.failure, + ); + }); + } + + Future _trailingOnLongPress() async => SonarrReleasesRouter.navigateTo( + context, + episodeId: widget.record.id, + ); +} diff --git a/lib/modules/sonarr/routes.dart b/lib/modules/sonarr/routes.dart deleted file mode 100644 index 107caf21..00000000 --- a/lib/modules/sonarr/routes.dart +++ /dev/null @@ -1,11 +0,0 @@ -export 'routes/sonarr.dart'; -export 'routes/catalogue.dart'; -export 'routes/history.dart'; -export 'routes/missing.dart'; -export 'routes/upcoming.dart'; -export 'routes/add_details.dart'; -export 'routes/add_search.dart'; -export 'routes/details_season.dart'; -export 'routes/details_series.dart'; -export 'routes/edit_series.dart'; -export 'routes/search_results.dart'; diff --git a/lib/modules/sonarr/routes/add_details.dart b/lib/modules/sonarr/routes/add_details.dart deleted file mode 100644 index d7277414..00000000 --- a/lib/modules/sonarr/routes/add_details.dart +++ /dev/null @@ -1,274 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrAddDetailsArguments { - final SonarrSearchData data; - - SonarrAddDetailsArguments({ - @required this.data, - }); -} - -class SonarrAddDetails extends StatefulWidget { - static const ROUTE_NAME = '/sonarr/add/details'; - - @override - State createState() => _State(); -} - -class _State extends State { - final GlobalKey _scaffoldKey = GlobalKey(); - SonarrAddDetailsArguments _arguments; - Future _future; - List _rootFolders = []; - List _qualityProfiles = []; - - @override - void initState() { - super.initState(); - SchedulerBinding.instance.scheduleFrameCallback((_) { - setState(() => _arguments = ModalRoute.of(context).settings.arguments); - _refresh(); - }); - } - - void _refresh() => setState(() { - _future = _fetchParameters(); - }); - - Future _fetchParameters() async { - SonarrAPI _api = SonarrAPI.from(Database.currentProfileObject); - return _fetchRootFolders(_api) - .then((_) => _fetchQualityProfiles(_api)) - .then((_) => _fetchSeriesTypes()) - .then((_) => _fetchMonitorStatuses()) - .then((_) {}) - .catchError((error) => Future.error(error)); - } - - Future _fetchRootFolders(SonarrAPI api) async { - return await api.getRootFolders() - .then((values) { - SonarrRootFolder _rootfolder = SonarrDatabaseValue.ADD_ROOT_FOLDER.data; - _rootFolders = values; - int index = _rootFolders.indexWhere((value) => - value.id == _rootfolder?.id && - value.path == _rootfolder?.path - ); - SonarrDatabaseValue.ADD_ROOT_FOLDER.put(index != -1 ? _rootFolders[index] : _rootFolders[0]); - }) - .catchError((error) { - Future.error(error); - }); - } - - Future _fetchSeriesTypes() async { - SonarrSeriesType _seriesType = SonarrDatabaseValue.ADD_SERIES_TYPE.data; - int index = SonarrConstants.SERIES_TYPES.indexWhere((value) => - value.type == _seriesType?.type, - ); - SonarrDatabaseValue.ADD_SERIES_TYPE.put(index != -1 ? SonarrConstants.SERIES_TYPES[index] : SonarrConstants.SERIES_TYPES[2]); - } - - Future _fetchMonitorStatuses() async { - SonarrMonitorStatus _monitorStatus = SonarrDatabaseValue.ADD_MONITOR_STATUS.data; - _monitorStatus ??= SonarrMonitorStatus.ALL; - SonarrDatabaseValue.ADD_MONITOR_STATUS.put(_monitorStatus); - } - - Future _fetchQualityProfiles(SonarrAPI api) async { - return await api.getQualityProfiles() - .then((values) { - SonarrQualityProfile _profile = SonarrDatabaseValue.ADD_QUALITY_PROFILE.data; - _qualityProfiles = values.values.toList(); - int index = _qualityProfiles.indexWhere((value) => - value.id == _profile?.id && - value.name == _profile?.name - ); - SonarrDatabaseValue.ADD_QUALITY_PROFILE.put(index != -1 ? _qualityProfiles[index] : _qualityProfiles[0]); - }) - .catchError((error) => error); - } - - @override - Widget build(BuildContext context) => Scaffold( - key: _scaffoldKey, - appBar: _appBar, - body: _body, - ); - - Widget get _appBar => _arguments == null - ? null - : LSAppBar( - title: _arguments.data.title, - actions: [ - LSIconButton( - icon: Icons.link, - onPressed: () async => _arguments.data.tvdbId == 0 - ? LSSnackBar( - context: context, - title: 'No TVDB Page Available', - message: 'No TVDB URL is available', - ) - : _arguments.data.tvdbId.toString().lsLinks_OpenTVDB(), - ) - ], - ); - - Widget get _body => _arguments == null - ? null - : FutureBuilder( - future: _future, - builder: (context, snapshot) { - switch(snapshot.connectionState) { - case ConnectionState.done: { - if(snapshot.hasError) return LSErrorMessage(onTapHandler: () => _refresh()); - return _list; - } - case ConnectionState.none: - case ConnectionState.waiting: - case ConnectionState.active: - default: return LSLoader(); - } - }, - ); - - Widget get _list => LSListView( - children: [ - LSDescriptionBlock( - title: _arguments.data.title ?? 'Unknown', - description: _arguments.data.overview == '' - ? 'No summary is available.' - : _arguments.data.overview, - uri: _arguments.data.posterURI ?? '', - fallbackImage: 'assets/images/sonarr/noseriesposter.png', - headers: Database.currentProfileObject.getSonarr()['headers'], - ), - LSDivider(), - ValueListenableBuilder( - valueListenable: Database.lunaSeaBox.listenable(keys: [SonarrDatabaseValue.ADD_MONITORED.key]), - builder: (context, box, widget) { - return LSCardTile( - title: LSTitle(text: 'Monitored'), - subtitle: LSSubtitle(text: 'Monitor series for new releases'), - trailing: Switch( - value: SonarrDatabaseValue.ADD_MONITORED.data, - onChanged: (value) => SonarrDatabaseValue.ADD_MONITORED.put(value), - ), - ); - } - ), - ValueListenableBuilder( - valueListenable: Database.lunaSeaBox.listenable(keys: [SonarrDatabaseValue.ADD_SEASON_FOLDERS.key]), - builder: (context, box, widget) { - return LSCardTile( - title: LSTitle(text: 'Use Season Folders'), - subtitle: LSSubtitle(text: 'Sort episodes into season folders'), - trailing: Switch( - value: SonarrDatabaseValue.ADD_SEASON_FOLDERS.data, - onChanged: (value) => SonarrDatabaseValue.ADD_SEASON_FOLDERS.put(value), - ), - ); - } - ), - ValueListenableBuilder( - valueListenable: Database.lunaSeaBox.listenable(keys: [SonarrDatabaseValue.ADD_MONITOR_STATUS.key]), - builder: (context, box, widget) { - SonarrMonitorStatus _status = SonarrDatabaseValue.ADD_MONITOR_STATUS.data; - return LSCardTile( - title: LSTitle(text: 'Monitoring Status'), - subtitle: LSSubtitle(text: _status.name ?? 'Unknown Status'), - trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: () async { - List _values = await SonarrDialogs.editMonitoringStatus(context); - if(_values[0]) SonarrDatabaseValue.ADD_MONITOR_STATUS.put(_values[1]); - }, - ); - }, - ), - ValueListenableBuilder( - valueListenable: Database.lunaSeaBox.listenable(keys: [SonarrDatabaseValue.ADD_QUALITY_PROFILE.key]), - builder: (context, box, widget) { - SonarrQualityProfile _profile = SonarrDatabaseValue.ADD_QUALITY_PROFILE.data; - return LSCardTile( - title: LSTitle(text: 'Quality Profile'), - subtitle: LSSubtitle(text: _profile?.name ?? 'Unknown Profile'), - trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: () async { - List _values = await SonarrDialogs.editQualityProfile(context, _qualityProfiles); - if(_values[0]) SonarrDatabaseValue.ADD_QUALITY_PROFILE.put(_values[1]); - }, - ); - }, - ), - ValueListenableBuilder( - valueListenable: Database.lunaSeaBox.listenable(keys: [SonarrDatabaseValue.ADD_ROOT_FOLDER.key]), - builder: (context, box, widget) { - SonarrRootFolder _rootfolder = SonarrDatabaseValue.ADD_ROOT_FOLDER.data; - return LSCardTile( - title: LSTitle(text: 'Root Folder'), - subtitle: LSSubtitle(text: _rootfolder?.path ?? 'Unknown Root Folder'), - trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: () async { - List _values = await SonarrDialogs.editRootFolder(context, _rootFolders); - if(_values[0]) SonarrDatabaseValue.ADD_ROOT_FOLDER.put(_values[1]); - }, - ); - }, - ), - ValueListenableBuilder( - valueListenable: Database.lunaSeaBox.listenable(keys: [SonarrDatabaseValue.ADD_SERIES_TYPE.key]), - builder: (context, box, widget) { - SonarrSeriesType _type = SonarrDatabaseValue.ADD_SERIES_TYPE.data; - return LSCardTile( - title: LSTitle(text: 'Series Type'), - subtitle: LSSubtitle(text: _type?.type?.lsLanguage_Capitalize() ?? 'Unknown Type'), - trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: () async { - List _values = await SonarrDialogs.editSeriesType(context); - if(_values[0]) SonarrDatabaseValue.ADD_SERIES_TYPE.put(_values[1]); - }, - ); - }, - ), - LSDivider(), - LSContainerRow( - children: [ - Expanded( - child: LSButton( - text: 'Add', - onTap: () async => _add(search: false), - reducedMargin: true, - ), - ), - Expanded( - child: LSButton( - text: 'Add + Search', - backgroundColor: LSColors.orange, - onTap: () async => _add(search: true), - reducedMargin: true, - ), - ), - ], - ) - ], - ); - - Future _add({ bool search = false }) async { - SonarrAPI _api = SonarrAPI.from(Database.currentProfileObject); - await _api.addSeries( - _arguments.data, - SonarrDatabaseValue.ADD_QUALITY_PROFILE.data, - SonarrDatabaseValue.ADD_ROOT_FOLDER.data, - SonarrDatabaseValue.ADD_SERIES_TYPE.data, - SonarrDatabaseValue.ADD_MONITOR_STATUS.data, - SonarrDatabaseValue.ADD_SEASON_FOLDERS.data ?? true, - SonarrDatabaseValue.ADD_MONITORED.data ?? true, - search: search, - ) - .then((id) => Navigator.of(context).pop(['series_added', _arguments.data.title, id])) - .catchError((_) => LSSnackBar(context: context, title: search ? 'Failed to Add Series (With Search)' : 'Failed to Add Series', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure)); - } -} diff --git a/lib/modules/sonarr/routes/add_search.dart b/lib/modules/sonarr/routes/add_search.dart deleted file mode 100644 index bc9a810c..00000000 --- a/lib/modules/sonarr/routes/add_search.dart +++ /dev/null @@ -1,107 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrAddSearch extends StatefulWidget { - static const ROUTE_NAME = '/sonarr/add/search'; - - @override - State createState() => _State(); -} - -class _State extends State { - final GlobalKey _scaffoldKey = GlobalKey(); - final GlobalKey _refreshKey = GlobalKey(); - final _scrollController = ScrollController(); - Future> _future; - List _results; - List _availableIDs = []; - - @override - void initState() { - super.initState(); - _fetchAvailableSeries(); - } - - Future _refresh() async { - final _model = Provider.of(context, listen: false); - final _api = SonarrAPI.from(Database.currentProfileObject); - setState(() { - _future = _api.searchSeries(_model.addSearchQuery); - }); - } - - Future _fetchAvailableSeries() async { - await SonarrAPI.from(Database.currentProfileObject).getAllSeriesIDs() - .then((data) => _availableIDs = data) - .catchError((_) => _availableIDs = []); - } - - @override - Widget build(BuildContext context) => Scaffold( - key: _scaffoldKey, - appBar: _appBar, - body: _body, - ); - - Widget get _appBar => LSAppBar(title: 'Add Series'); - - Widget get _body => LSRefreshIndicator( - refreshKey: _refreshKey, - onRefresh: _refresh, - child: FutureBuilder( - future: _future, - builder: (context, snapshot) { - List _data; - switch(snapshot.connectionState) { - case ConnectionState.done: { - if(snapshot.hasError || snapshot.data == null) { - _data = _error; - } else { - _results = snapshot.data; - _data = _assembleResults; - } - break; - } - case ConnectionState.none: _data = []; break; - case ConnectionState.waiting: - case ConnectionState.active: - default: _data = _loading; break; - } - return _list(_data); - }, - ), - ); - - Widget _list(List data) => LSListViewStickyHeader( - controller: _scrollController, - slivers: [ - LSStickyHeader( - header: _searchBar, - children: data, - ), - ], - ); - - Widget get _searchBar => LSContainerRow( - padding: EdgeInsets.zero, - backgroundColor: Theme.of(context).primaryColor, - children: [ - SonarrAddSearchBar(callback: _refresh), - ], - ); - - List get _loading => [LSTypewriterMessage(text: 'Searching...')]; - - List get _error => [LSErrorMessage(onTapHandler: () => _refresh(), hideButton: true)]; - - List get _assembleResults => _results.length > 0 - ? List.generate( - _results.length, - (index) => SonarrAddSearchResultTile( - data: _results[index], - alreadyAdded: _availableIDs.contains(_results[index].tvdbId), - ), - ) - : [LSGenericMessage(text: 'No Results Found')]; -} \ No newline at end of file diff --git a/lib/modules/sonarr/routes/catalogue.dart b/lib/modules/sonarr/routes/catalogue.dart deleted file mode 100644 index a7a451a4..00000000 --- a/lib/modules/sonarr/routes/catalogue.dart +++ /dev/null @@ -1,139 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrCatalogue extends StatefulWidget { - static const ROUTE_NAME = '/sonarr/catalogue'; - final GlobalKey refreshIndicatorKey; - final Function refreshAllPages; - - SonarrCatalogue({ - Key key, - @required this.refreshIndicatorKey, - @required this.refreshAllPages, - }) : super(key: key); - - @override - State createState() => _State(); -} - -class _State extends State with AutomaticKeepAliveClientMixin { - final _scaffoldKey = GlobalKey(); - final _scrollController = ScrollController(); - Future> _future; - List _results = []; - - @override - bool get wantKeepAlive => true; - - @override - void initState() { - super.initState(); - _refresh(); - } - - Future _refresh() async { - if(mounted) setState(() => _results = []); - final _api = SonarrAPI.from(Database.currentProfileObject); - if(mounted) setState(() => { _future = _api.getAllSeries() }); - //Clear the search filter using a microtask - Future.microtask(() => Provider.of(context, listen: false)?.searchCatalogueFilter = ''); - } - - void _refreshState() => setState(() {}); - - void _refreshAllPages() => widget.refreshAllPages(); - - @override - Widget build(BuildContext context) { - super.build(context); - return Scaffold( - key: _scaffoldKey, - body: _body, - ); - } - - Widget get _body => LSRefreshIndicator( - refreshKey: widget.refreshIndicatorKey, - onRefresh: _refresh, - child: FutureBuilder( - future: _future, - builder: (context, snapshot) { - switch(snapshot.connectionState) { - case ConnectionState.done: { - if(snapshot.hasError || snapshot.data == null) return LSErrorMessage(onTapHandler: () => _refresh()); - _results = snapshot.data; - return _list; - } - case ConnectionState.none: - case ConnectionState.waiting: - case ConnectionState.active: - default: return LSLoader(); - } - }, - ), - ); - - Widget get _searchSortBar => LSContainerRow( - padding: EdgeInsets.zero, - backgroundColor: Theme.of(context).primaryColor, - children: [ - SonarrCatalogueSearchBar(), - SonarrCatalogueHideButton(controller: _scrollController), - SonarrCatalogueSortButton(controller: _scrollController), - ], - ); - - Widget get _list => _results.length == 0 - ? LSGenericMessage( - text: 'No Series Found', - showButton: true, - buttonText: 'Refresh', - onTapHandler: () => _refresh(), - ) - : Consumer( - builder: (context, model, widget) { - List _filtered = _sort(model, _filter(model.searchCatalogueFilter)); - _filtered = model.hideUnmonitoredSeries ? _hide(_filtered) : _filtered; - return _listBody(_filtered); - } - ); - - Widget _listBody(List filtered) { - List _children = filtered.length == 0 - ? [LSGenericMessage(text: 'No Results Found')] - : List.generate( - filtered.length, - (index) => SonarrCatalogueTile( - data: filtered[index], - scaffoldKey: _scaffoldKey, - refresh: () => _refreshAllPages(), - refreshState: () => _refreshState(), - ), - ); - return LSListViewStickyHeader( - controller: _scrollController, - slivers: [ - LSStickyHeader( - header: _searchSortBar, - children: _children, - ), - ], - ); - } - - List _filter(String filter) => _results.where( - (entry) => filter == null || filter == '' - ? entry != null - : entry.title.toLowerCase().contains(filter.toLowerCase()) - ).toList(); - - List _sort(SonarrModel model, List data) { - if(data != null && data.length != 0) return model.sortCatalogueType.sort(data, model.sortCatalogueAscending); - return data; - } - - List _hide(List data) => data == null || data.length == 0 - ? data - : data.where((entry) => entry.monitored).toList(); -} \ No newline at end of file diff --git a/lib/modules/sonarr/routes/details_season.dart b/lib/modules/sonarr/routes/details_season.dart deleted file mode 100644 index 23b5c09d..00000000 --- a/lib/modules/sonarr/routes/details_season.dart +++ /dev/null @@ -1,206 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrDetailsSeasonArguments { - final String title; - final int seriesID; - final int season; - - SonarrDetailsSeasonArguments({ - @required this.season, - @required this.seriesID, - @required this.title, - }); -} - -class SonarrDetailsSeason extends StatefulWidget { - static const ROUTE_NAME = '/sonarr/details/season'; - - @override - State createState() => _State(); -} - -class _State extends State { - final _scaffoldKey = GlobalKey(); - final _refreshIndicatorKey = GlobalKey(); - SonarrDetailsSeasonArguments _arguments; - - Future> _future; - Map _results; - List _selected = []; - - @override - void initState() { - super.initState(); - SchedulerBinding.instance.addPostFrameCallback((_) { - setState(() => { _arguments = ModalRoute.of(context).settings.arguments }); - _refresh(); - }); - } - - @override - Widget build(BuildContext context) => Scaffold( - key: _scaffoldKey, - appBar: _appBar, - body: _body, - floatingActionButton: _floatingActionButton, - ); - - Future _refresh() async { - SonarrAPI _api = SonarrAPI.from(Database.currentProfileObject); - setState(() { - _future = _api.getEpisodes(_arguments.seriesID, _arguments.season); - }); - } - - Widget get _appBar => _arguments == null - ? null - : LSAppBar(title: _arguments.title); - - Widget get _floatingActionButton => _selected.length == 0 - ? null - : LSFloatingActionButtonExtended( - label: _selected.length == 1 ? '1 Episode' : '${_selected.length} Episodes', - icon: Icons.search, - onPressed: () => _searchSelected(), - ); - - Widget get _body => LSRefreshIndicator( - refreshKey: _refreshIndicatorKey, - onRefresh: () => _refresh(), - child: FutureBuilder( - future: _future, - builder: (context, snapshot) { - switch(snapshot.connectionState) { - case ConnectionState.done: { - if(snapshot.hasError || snapshot.data == null) return LSErrorMessage(onTapHandler: () => _refresh()); - _results = snapshot.data; - return _list; - } - case ConnectionState.none: - case ConnectionState.waiting: - case ConnectionState.active: - default: return LSLoader(); - } - }, - ), - ); - - Widget get _list => !_hasEpisodes - ? LSGenericMessage( - text: 'No Episodes Found', - showButton: true, - buttonText: 'Refresh', - onTapHandler: () => _refresh(), - ) - : _arguments.season == -1 - ? _allSeasons - : _singleSeason; - - Widget get _allSeasons { - List> _seasons = []; - for(var entry in _results.keys) { - if(entry != -1) _seasons.add(_season(entry)); - } - return LSListView( - children: _seasons.reversed.expand((element) => element).toList(), - ); - } - - Widget get _singleSeason => LSListView( - children: _season(_arguments.season), - ); - - List _season(int seasonNumber) { - List episodeCards = []; - for(int i=0; i<_results[seasonNumber].length; i++) episodeCards.add(SonarrEpisodeTile( - data: _results[seasonNumber][_results[seasonNumber].length-i-1], - selectedCallback: (status, episodeID) => _selectedCallback(status, episodeID), - )); - return [ - GestureDetector( - child: seasonNumber == 0 - ? LSHeader(text: 'Specials') - : LSHeader(text: 'Season $seasonNumber'), - onTap: () => _selectSeason(episodeCards, seasonNumber), - onLongPress: () => _searchSeason(seasonNumber), - ), - ...episodeCards, - ]; - } - - void _selectedCallback(bool status, int episodeID) => status - ? setState(() => _selected.add(episodeID)) - : setState(() => _selected.remove(episodeID)); - - bool get _hasEpisodes { - if(_results == null || _results.length == 0) return false; - return true; - } - - void _selectSeason(List episodeCards, int seasonNumber) { - bool flag = false; - for(SonarrEpisodeData data in _results[seasonNumber]) { - if(!data.isSelected) { - flag = true; - break; - } - } - for(SonarrEpisodeData data in _results[seasonNumber]) { - data.isSelected = flag ? true : false; - if(flag) { - if(!_selected.contains(data.episodeID)) - _selected.add(data.episodeID); - } else { - _selected.remove(data.episodeID); - } - } - setState(() {}); - } - - Future _searchSelected() async { - for(var key in _results.keys) { - if(key != -1) for(SonarrEpisodeData data in _results[key]) { - data.isSelected = false; - } - } - await SonarrAPI.from(Database.currentProfileObject).searchEpisodes(_selected) - .then((_) => LSSnackBar( - context: context, - title: 'Searching for Episodes', - message: 'Searching for ${_selected.length} ${_selected.length == 1 ? 'episode' : 'episodes'}', - )) - .catchError((_) => LSSnackBar( - context: context, - title: 'Failed to Search for Selected Episodes', - message: Constants.CHECK_LOGS_MESSAGE, - type: SNACKBAR_TYPE.failure, - )); - setState(() { - _selected.clear(); - }); - } - - Future _searchSeason(int season) async { - List _values = await SonarrDialogs.searchEntireSeason(context, season); - if(_values[0]) { - SonarrAPI _api = SonarrAPI.from(Database.currentProfileObject); - await _api.searchSeason(_arguments.seriesID, season) - .then((_) => LSSnackBar( - context: context, - title: 'Searching...', - message: season == 0 - ? 'Searching for all episodes in specials' - : 'Searching for all episodes in season $season', - )) - .catchError((_) => LSSnackBar( - context: context, - title: 'Failed to Search', - message: Constants.CHECK_LOGS_MESSAGE, - type: SNACKBAR_TYPE.failure, - )); - } - } -} diff --git a/lib/modules/sonarr/routes/details_series.dart b/lib/modules/sonarr/routes/details_series.dart deleted file mode 100644 index 6fe0daee..00000000 --- a/lib/modules/sonarr/routes/details_series.dart +++ /dev/null @@ -1,99 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrDetailsSeriesArguments { - SonarrCatalogueData data; - final int seriesID; - - SonarrDetailsSeriesArguments({ - @required this.data, - @required this.seriesID, - }); -} - -class SonarrDetailsSeries extends StatefulWidget { - static const ROUTE_NAME = '/sonarr/details/series'; - - @override - State createState() => _State(); -} - -class _State extends State { - final GlobalKey _scaffoldKey = GlobalKey(); - final _pageController = PageController(initialPage: 1); - SonarrDetailsSeriesArguments _arguments; - bool _error = false; - - @override - void initState() { - super.initState(); - SchedulerBinding.instance.addPostFrameCallback((_) { - _arguments = ModalRoute.of(context).settings.arguments; - Provider.of(context, listen: false).seriesNavigationIndex = 1; - _fetch(); - }); - } - - Future _fetch() async { - if(mounted) setState(() => _error = false); - if(_arguments != null) await SonarrAPI.from(Database.currentProfileObject).getSeries(_arguments.seriesID) - .then((data) { - if(mounted) setState(() { - _arguments.data = data; - _error = false; - }); - }) - .catchError((_) { - if(mounted) setState(() => _error = true); - }); - } - - @override - Widget build(BuildContext context) => Scaffold( - key: _scaffoldKey, - appBar: _appBar, - bottomNavigationBar: _arguments != null && _arguments.data != null - ? _bottomNavigationBar - : null, - body: _arguments != null - ? _arguments.data != null - ? _body - : _error - ? LSErrorMessage(onTapHandler: () => _fetch()) - : LSLoader() - : null, - ); - - Widget get _appBar => LSAppBar( - title: _arguments == null || _arguments.data == null - ? 'Series Details' - : _arguments.data.title, - actions: _arguments == null || _arguments.data == null - ? null - : [ - SonarrDetailsEditButton( - data: _arguments.data, - remove: (bool withData) => _removeCallback(withData), - ), - ], - ); - - Widget get _bottomNavigationBar => SonarrSeriesNavigationBar(pageController: _pageController); - - List get _tabs => [ - SonarrDetailsOverview(data: _arguments.data), - SonarrDetailsSeasonList(data: _arguments.data), - ]; - - Widget get _body => PageView( - controller: _pageController, - children: _tabs, - onPageChanged: _onPageChanged, - ); - - void _onPageChanged(int index) => Provider.of(context, listen: false).seriesNavigationIndex = index; - - Future _removeCallback(bool withData) async => Navigator.of(context).pop(['remove_series', withData]); -} \ No newline at end of file diff --git a/lib/modules/sonarr/routes/edit_series.dart b/lib/modules/sonarr/routes/edit_series.dart deleted file mode 100644 index e6ac1933..00000000 --- a/lib/modules/sonarr/routes/edit_series.dart +++ /dev/null @@ -1,175 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrEditSeriesArguments { - final SonarrCatalogueData data; - - SonarrEditSeriesArguments({ - @required this.data, - }); -} - -class SonarrEditSeries extends StatefulWidget { - static const ROUTE_NAME = '/sonarr/edit/series'; - - @override - State createState() => _State(); -} - -class _State extends State { - final GlobalKey _scaffoldKey = GlobalKey(); - - SonarrEditSeriesArguments _arguments; - Future _future; - - List _qualityProfiles = []; - SonarrQualityProfile _qualityProfile; - SonarrSeriesType _seriesType; - - String _path; - bool _monitored; - bool _seasonFolders; - - @override - void initState() { - super.initState(); - SchedulerBinding.instance.addPostFrameCallback((_) { - setState(() => _arguments = ModalRoute.of(context).settings.arguments); - _refresh(); - }); - } - - Future _refresh() async => setState(() { _future = _fetch().catchError((error) {}); }); - - Future _fetch() async { - final _api = SonarrAPI.from(Database.currentProfileObject); - return _fetchProfiles(_api) - .then((_) { - int index = SonarrConstants.SERIES_TYPES.indexWhere((type) => type.type == _arguments.data.type); - _seriesType = SonarrConstants.SERIES_TYPES[index == -1 ? 0 : index]; - _path = _arguments.data.path; - _monitored = _arguments.data.monitored; - _seasonFolders = _arguments.data.seasonFolder; - return true; - }) - .catchError((error) => Future.error(error)); - } - - Future _fetchProfiles(SonarrAPI api) async { - return await api.getQualityProfiles() - .then((profiles) { - _qualityProfiles = profiles?.values?.toList(); - if(_qualityProfiles != null && _qualityProfiles.length > 0) - _qualityProfile = _qualityProfiles.firstWhere((profile) => profile.id == _arguments.data.qualityProfile); - }) - .catchError((error) => error); - } - - @override - Widget build(BuildContext context) => Scaffold( - key: _scaffoldKey, - appBar: _appBar, - body: _body, - ); - - Widget get _appBar => LSAppBar(title: _arguments?.data?.title ?? 'Edit Series'); - - Widget get _body => FutureBuilder( - future: _future, - builder: (context, snapshot) { - switch(snapshot.connectionState) { - case ConnectionState.done: { - if(snapshot.hasError || snapshot.data == null) return LSErrorMessage(onTapHandler: () => _refresh()); - return _list; - } - case ConnectionState.none: - case ConnectionState.waiting: - case ConnectionState.active: - default: return LSLoader(); - } - }, - ); - - Widget get _list => LSListView( - children: [ - LSCardTile( - title: LSTitle(text: 'Monitored'), - subtitle: LSSubtitle(text: 'Monitor series for new releases'), - trailing: Switch( - value: _monitored, - onChanged: (value) => setState(() => _monitored = value), - ), - ), - LSCardTile( - title: LSTitle(text: 'Season Folders'), - subtitle: LSSubtitle(text: 'Sort episodes into season folders'), - trailing: Switch( - value: _seasonFolders, - onChanged: (value) => setState(() => _seasonFolders = value), - ), - ), - LSCardTile( - title: LSTitle(text: 'Series Path'), - subtitle: LSSubtitle(text: _path), - trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: () => _changePath(), - ), - LSCardTile( - title: LSTitle(text: 'Quality Profile'), - subtitle: LSSubtitle(text: _qualityProfile.name), - trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: () => _changeProfile(), - ), - LSCardTile( - title: LSTitle(text: 'Series Type'), - subtitle: LSSubtitle(text: _seriesType.type.lsLanguage_Capitalize()), - trailing: LSIconButton(icon: Icons.arrow_forward_ios), - onTap: () => _changeType(), - ), - LSDivider(), - LSButton( - text: 'Update Series', - onTap: () async => _save().catchError((_) {}), - ), - ], - ); - - Future _changePath() async { - List _values = await GlobalDialogs.editText(context, 'Series Path', prefill: _path); - if(_values[0] && mounted) setState(() => _path = _values[1]); - } - - Future _changeProfile() async { - List _values = await SonarrDialogs.editQualityProfile(context, _qualityProfiles); - if(_values[0] && mounted) setState(() => _qualityProfile = _values[1]); - } - - Future _changeType() async { - List _values = await SonarrDialogs.editSeriesType(context); - if(_values[0] && mounted) setState(() => _seriesType = _values[1]); - } - - Future _save() async { - final _api = SonarrAPI.from(Database.currentProfileObject); - await _api.editSeries( - _arguments.data.seriesID, - _qualityProfile, - _seriesType, - _path, - _monitored, - _seasonFolders, - ) - .then((_) { - _arguments.data.qualityProfile = _qualityProfile.id; - _arguments.data.profile = _qualityProfile.name; - _arguments.data.type = _seriesType.type; - _arguments.data.seasonFolder = _seasonFolders; - _arguments.data.path = _path; - _arguments.data.monitored = _monitored; - Navigator.of(context).pop([true]); - }) - .catchError((_) => LSSnackBar(context: context, title: 'Failed to Update', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure)); - } -} \ No newline at end of file diff --git a/lib/modules/sonarr/routes/history.dart b/lib/modules/sonarr/routes/history.dart deleted file mode 100644 index 369c44a0..00000000 --- a/lib/modules/sonarr/routes/history.dart +++ /dev/null @@ -1,89 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrHistory extends StatefulWidget { - static const ROUTE_NAME = '/sonarr/history'; - final GlobalKey refreshIndicatorKey; - final Function refreshAllPages; - - SonarrHistory({ - Key key, - @required this.refreshIndicatorKey, - @required this.refreshAllPages, - }) : super(key: key); - - @override - State createState() => _State(); -} - -class _State extends State with AutomaticKeepAliveClientMixin { - final _scaffoldKey = GlobalKey(); - Future> _future; - List _results = []; - - @override - bool get wantKeepAlive => true; - - @override - void initState() { - super.initState(); - _refresh(); - } - - Future _refresh() async { - _results = []; - final _api = SonarrAPI.from(Database.currentProfileObject); - if(mounted) setState(() { - _future = _api.getHistory(); - }); - } - - void _refreshAllPages() => widget.refreshAllPages(); - - @override - Widget build(BuildContext context) { - super.build(context); - return Scaffold( - key: _scaffoldKey, - body: _body, - ); - } - - Widget get _body => LSRefreshIndicator( - refreshKey: widget.refreshIndicatorKey, - onRefresh: _refresh, - child: FutureBuilder( - future: _future, - builder: (context, snapshot) { - switch(snapshot.connectionState) { - case ConnectionState.done: { - if(snapshot.hasError || snapshot.data == null) return LSErrorMessage(onTapHandler: () => _refresh()); - _results = snapshot.data; - return _list; - } - case ConnectionState.none: - case ConnectionState.waiting: - case ConnectionState.active: - default: return LSLoader(); - } - }, - ), - ); - - Widget get _list => _results.length == 0 - ? LSGenericMessage( - text: 'No History Found', - showButton: true, - buttonText: 'Refresh', - onTapHandler: () => _refresh(), - ) - : LSListViewBuilder( - itemCount: _results.length, - itemBuilder: (context, index) => SonarrHistoryTile( - data: _results[index], - scaffoldKey: _scaffoldKey, - refresh: () => _refreshAllPages(), - ), - ); -} diff --git a/lib/modules/sonarr/routes/missing.dart b/lib/modules/sonarr/routes/missing.dart deleted file mode 100644 index f38730c6..00000000 --- a/lib/modules/sonarr/routes/missing.dart +++ /dev/null @@ -1,89 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrMissing extends StatefulWidget { - static const ROUTE_NAME = '/sonarr/missing'; - final GlobalKey refreshIndicatorKey; - final Function refreshAllPages; - - SonarrMissing({ - Key key, - @required this.refreshIndicatorKey, - @required this.refreshAllPages, - }) : super(key: key); - - @override - State createState() => _State(); -} - -class _State extends State with AutomaticKeepAliveClientMixin { - final _scaffoldKey = GlobalKey(); - Future> _future; - List _results = []; - - @override - bool get wantKeepAlive => true; - - @override - void initState() { - super.initState(); - _refresh(); - } - - Future _refresh() async { - _results = []; - final _api = SonarrAPI.from(Database.currentProfileObject); - if(mounted) setState(() { - _future = _api.getMissing(); - }); - } - - void _refreshAllPages() => widget.refreshAllPages(); - - @override - Widget build(BuildContext context) { - super.build(context); - return Scaffold( - key: _scaffoldKey, - body: _body, - ); - } - - Widget get _body => LSRefreshIndicator( - refreshKey: widget.refreshIndicatorKey, - onRefresh: _refresh, - child: FutureBuilder( - future: _future, - builder: (context, snapshot) { - switch(snapshot.connectionState) { - case ConnectionState.done: { - if(snapshot.hasError || snapshot.data == null) return LSErrorMessage(onTapHandler: () => _refresh()); - _results = snapshot.data; - return _list; - } - case ConnectionState.none: - case ConnectionState.waiting: - case ConnectionState.active: - default: return LSLoader(); - } - }, - ), - ); - - Widget get _list => _results.length == 0 - ? LSGenericMessage( - text: 'No Missing Episodes', - showButton: true, - buttonText: 'Refresh', - onTapHandler: () => _refresh(), - ) - : LSListViewBuilder( - itemCount: _results.length, - itemBuilder: (context, index) => SonarrMissingTile( - scaffoldKey: _scaffoldKey, - data: _results[index], - refresh: () => _refreshAllPages(), - ), - ); -} \ No newline at end of file diff --git a/lib/modules/sonarr/routes/search_results.dart b/lib/modules/sonarr/routes/search_results.dart deleted file mode 100644 index 21adf931..00000000 --- a/lib/modules/sonarr/routes/search_results.dart +++ /dev/null @@ -1,140 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrSearchResultsArguments { - final int episodeID; - final String title; - - SonarrSearchResultsArguments({ - @required this.episodeID, - @required this.title, - }); -} - -class SonarrSearchResults extends StatefulWidget { - static const ROUTE_NAME = '/sonarr/search/results'; - - @override - State createState() => _State(); -} - -class _State extends State { - final GlobalKey _scaffoldKey = GlobalKey(); - final GlobalKey _refreshKey = GlobalKey(); - final ScrollController _scrollController = ScrollController(); - - SonarrSearchResultsArguments _arguments; - Future> _future; - List _results; - - @override - void initState() { - super.initState(); - SchedulerBinding.instance.scheduleFrameCallback((_) { - setState(() => _arguments = ModalRoute.of(context).settings.arguments); - _refresh(); - }); - } - - Future _refresh() async { - if(mounted) setState(() => _results = []); - final _api = SonarrAPI.from(Database.currentProfileObject); - setState(() => { _future = _api.getReleases(_arguments.episodeID) }); - //Clear the search filter using a microtask - Future.microtask(() => Provider.of(context, listen: false)?.searchReleasesFilter = ''); - } - - @override - Widget build(BuildContext context) => Scaffold( - key: _scaffoldKey, - appBar: _appBar, - body: _body, - ); - - Widget get _appBar => _arguments == null - ? null - : LSAppBar(title: _arguments.title); - - Widget get _body => _arguments == null - ? null - : LSRefreshIndicator( - refreshKey: _refreshKey, - onRefresh: () => _refresh(), - child: FutureBuilder( - future: _future, - builder: (context, snapshot) { - switch(snapshot.connectionState) { - case ConnectionState.done: { - if(snapshot.hasError || snapshot.data == null) return LSErrorMessage(onTapHandler: () => _refresh()); - _results = snapshot.data; - return _list; - } - case ConnectionState.none: - case ConnectionState.waiting: - case ConnectionState.active: - default: return LSLoader(); - } - }, - ), - ); - - Widget get _searchSortBar => LSContainerRow( - padding: EdgeInsets.zero, - backgroundColor: Theme.of(context).primaryColor, - children: [ - SonarrReleasesSearchBar(), - SonarrReleasesHideButton(controller: _scrollController), - SonarrReleasesSortButton(controller: _scrollController), - ], - ); - - Widget get _list => _results.length == 0 - ? LSGenericMessage( - text: 'No Results Found', - showButton: true, - buttonText: 'Refresh', - onTapHandler: () => _refresh(), - ) - : Consumer( - builder: (context, model, widget) { - List _filtered = _sort(model, _filter(model.searchReleasesFilter)); - _filtered = model.hideRejectedReleases ? _hide(_filtered) : _filtered; - return _listBody(_filtered); - }, - ); - - Widget _listBody(List filtered) { - List _children = filtered.length == 0 - ? [LSGenericMessage(text: 'No Results Found')] - : List.generate( - filtered.length, - (index) => SonarrSearchResultTile(data: filtered[index]), - ); - return LSListViewStickyHeader( - controller: _scrollController, - slivers: [ - LSStickyHeader( - header: _searchSortBar, - children: _children, - ) - ], - ); - } - - List _filter(String filter) => _results.where( - (entry) => filter == null || filter == '' - ? entry != null - : entry.title.toLowerCase().contains(filter.toLowerCase()) - ).toList(); - - List _sort(SonarrModel model, List data) { - if(data != null && data.length != 0) return model.sortReleasesType.sort(data, model.sortReleasesAscending); - return data; - } - - List _hide(List data) => data == null || data.length == 0 - ? data - : data.where((entry) => entry.approved).toList(); -} diff --git a/lib/modules/sonarr/routes/sonarr.dart b/lib/modules/sonarr/routes/sonarr.dart deleted file mode 100644 index cf691cc5..00000000 --- a/lib/modules/sonarr/routes/sonarr.dart +++ /dev/null @@ -1,174 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class Sonarr extends StatefulWidget { - static const ROUTE_NAME = '/sonarr'; - - @override - State createState() => _State(); -} - -class _State extends State { - final _scaffoldKey = GlobalKey(); - final _pageController = PageController(initialPage: SonarrDatabaseValue.NAVIGATION_INDEX.data); - String _profileState = Database.currentProfileObject.toString(); - SonarrAPI _api = SonarrAPI.from(Database.currentProfileObject); - - final List _refreshKeys = [ - GlobalKey(), - GlobalKey(), - GlobalKey(), - GlobalKey(), - ]; - - @override - void initState() { - super.initState(); - Future.microtask(() => Provider.of(context, listen: false).navigationIndex = 0); - } - - @override - Widget build(BuildContext context) => WillPopScope( - onWillPop: () async { - if(_scaffoldKey.currentState.isDrawerOpen) { - //If the drawer is open, return true to close it - return true; - } else { - //If the drawer isn't open, open the drawer - _scaffoldKey.currentState.openDrawer(); - return false; - } - }, - child: ValueListenableBuilder( - valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]), - builder: (context, box, widget) { - if(_profileState != Database.currentProfileObject.toString()) _refreshProfile(); - return Scaffold( - key: _scaffoldKey, - body: _body, - drawer: _drawer, - appBar: _appBar, - bottomNavigationBar: _bottomNavigationBar, - ); - }, - ), - ); - - Widget get _drawer => LSDrawer(page: 'sonarr'); - - Widget get _bottomNavigationBar => SonarrNavigationBar(pageController: _pageController); - - List get _tabs => [ - SonarrCatalogue( - refreshIndicatorKey: _refreshKeys[0], - refreshAllPages: _refreshAllPages, - ), - SonarrUpcoming( - refreshIndicatorKey: _refreshKeys[1], - refreshAllPages: _refreshAllPages, - ), - SonarrMissing( - refreshIndicatorKey: _refreshKeys[2], - refreshAllPages: _refreshAllPages, - ), - SonarrHistory( - refreshIndicatorKey: _refreshKeys[3], - refreshAllPages: _refreshAllPages, - ), - ]; - - Widget get _body => PageView( - controller: _pageController, - children: _api.enabled ? _tabs : List.generate(_tabs.length, (_) => LSNotEnabled('Sonarr')), - onPageChanged: _onPageChanged, - ); - - Widget get _appBar => LSAppBarDropdown( - context: context, - title: 'Sonarr', - profiles: Database.profilesBox.keys.fold([], (value, element) { - if((Database.profilesBox.get(element) as ProfileHiveObject).sonarrEnabled) - value.add(element); - return value; - }), - actions: _api.enabled - ? [ - LSIconButton( - icon: Icons.add, - onPressed: () async => _enterAddSeries(), - ), - LSIconButton( - icon: Icons.more_vert, - onPressed: () async => _handlePopup(), - ) - ] - : null, - ); - - Future _enterAddSeries() async { - final _model = Provider.of(context, listen: false); - _model.addSearchQuery = ''; - final dynamic result = await Navigator.of(context).pushNamed(SonarrAddSearch.ROUTE_NAME); - if(result != null) switch(result[0]) { - case 'series_added': { - LSSnackBar( - context: context, - title: 'Series Added', - message: result[1], - type: SNACKBAR_TYPE.success, - showButton: true, - buttonOnPressed: () => Navigator.of(context).pushNamed( - SonarrDetailsSeries.ROUTE_NAME, - arguments: SonarrDetailsSeriesArguments( - seriesID: result[2], - data: null, - ), - ), - ); - _refreshAllPages(); - break; - } - default: Logger.warning('Sonarr', '_enterAddSeries', 'Unknown Case: ${result[0]}'); - } - } - - Future _handlePopup() async { - List values = await SonarrDialogs.globalSettings(context); - if(values[0]) switch(values[1]) { - case 'web_gui': await _api.host?.toString()?.lsLinks_OpenLink(); break; - case 'update_library': await _api.updateLibrary() - .then((_) => LSSnackBar(context: context, title: 'Updating Library...', message: 'Updating your library in the background')) - .catchError((_) => LSSnackBar(context: context, title: 'Failed to Update Library', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure)); - break; - case 'rss_sync': await _api.triggerRssSync() - .then((_) => LSSnackBar(context: context, title: 'Running RSS Sync...', message: 'Running RSS sync in the background')) - .catchError((_) => LSSnackBar(context: context, title: 'Failed to Run RSS Sync', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure)); - break; - case 'backup': await _api.triggerBackup() - .then((_) => LSSnackBar(context: context, title: 'Backing Up Database...', message: 'Backing up database in the background')) - .catchError((_) => LSSnackBar(context: context, title: 'Failed to Backup Database', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure)); - break; - case 'missing_search': { - List values = await SonarrDialogs.searchAllMissing(context); - if(values[0]) await _api.searchAllMissing() - .then((_) => LSSnackBar(context: context, title: 'Searching...', message: 'Search for all missing episodes')) - .catchError((_) => LSSnackBar(context: context, title: 'Failed to Search', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure)); - break; - } - default: Logger.warning('Sonarr', '_handlePopup', 'Unknown Case: ${values[1]}'); - } - } - - void _onPageChanged(int index) => Provider.of(context, listen: false).navigationIndex = index; - - void _refreshProfile() { - _api = SonarrAPI.from(Database.currentProfileObject); - _profileState = Database.currentProfileObject.toString(); - _refreshAllPages(); - } - - void _refreshAllPages() { - for(var key in _refreshKeys) key?.currentState?.show(); - } -} \ No newline at end of file diff --git a/lib/modules/sonarr/routes/upcoming.dart b/lib/modules/sonarr/routes/upcoming.dart deleted file mode 100644 index 7264d187..00000000 --- a/lib/modules/sonarr/routes/upcoming.dart +++ /dev/null @@ -1,114 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrUpcoming extends StatefulWidget { - static const ROUTE_NAME = '/sonarr/upcoming'; - final GlobalKey refreshIndicatorKey; - final Function refreshAllPages; - - SonarrUpcoming({ - Key key, - @required this.refreshIndicatorKey, - @required this.refreshAllPages, - }) : super(key: key); - - @override - State createState() => _State(); -} - -class _State extends State with AutomaticKeepAliveClientMixin { - final _scaffoldKey = GlobalKey(); - Future _future; - Map _results = {}; - - @override - bool get wantKeepAlive => true; - - @override - void initState() { - super.initState(); - _refresh(); - } - - Future _refresh() async { - _results = {}; - final _api = SonarrAPI.from(Database.currentProfileObject); - if(mounted) setState(() { - _future = _api.getUpcoming(); - }); - } - - @override - Widget build(BuildContext context) { - super.build(context); - return Scaffold( - key: _scaffoldKey, - body: _body, - ); - } - - Widget get _body => LSRefreshIndicator( - refreshKey: widget.refreshIndicatorKey, - onRefresh: () => _refresh(), - child: FutureBuilder( - future: _future, - builder: (context, snapshot) { - switch(snapshot.connectionState) { - case ConnectionState.done: { - if(snapshot.hasError || snapshot.data == null) return LSErrorMessage(onTapHandler: () => _refresh()); - _results = snapshot.data; - return _list; - } - case ConnectionState.none: - case ConnectionState.waiting: - case ConnectionState.active: - default: return LSLoader(); - } - }, - ), - ); - - Widget get _list => !_hasEpisodes - ? LSGenericMessage( - text: 'No Upcoming Episodes', - showButton: true, - buttonText: 'Refresh', - onTapHandler: () => _refresh(), - ) - : _days; - - Widget get _days { - List> days = []; - for(var key in _results.keys) if(_results[key]['entries'].length > 0) days.add(_day(key)); - return LSListView( - children: days.expand((element) => element).toList(), - ); - } - - List _day(String day) { - List episodeCards = []; - for(int i=0; i<_results[day]['entries'].length; i++) episodeCards.add(SonarrUpcomingTile( - data: _results[day]['entries'][i], - refresh: () => _refresh(), - scaffoldKey: _scaffoldKey, - )); - return [ - LSHeader( - text: _results[day]['date'], - // subtitle: _results[day]['entries'].length == 1 - // ? '1 Episode' - // : '${_results[day]['entries'].length} Episodes', - ), - ...episodeCards, - ]; - } - - bool get _hasEpisodes { - if(_results == null || _results.length == 0) return false; - for(var key in _results.keys) { - if(_results[key]['entries'].length > 0) return true; - } - return false; - } -} diff --git a/lib/modules/sonarr/widgets.dart b/lib/modules/sonarr/widgets.dart deleted file mode 100644 index 54205ca8..00000000 --- a/lib/modules/sonarr/widgets.dart +++ /dev/null @@ -1,20 +0,0 @@ -export 'widgets/catalogue_tile.dart'; -export 'widgets/catalogue_search_bar.dart'; -export 'widgets/catalogue_sorting_button.dart'; -export 'widgets/catalogue_hide_button.dart'; -export 'widgets/missing_tile.dart'; -export 'widgets/history_tile.dart'; -export 'widgets/upcoming_tile.dart'; -export 'widgets/details_overview.dart'; -export 'widgets/details_season_list.dart'; -export 'widgets/details_season_list_tile.dart'; -export 'widgets/details_edit_button.dart'; -export 'widgets/add_search_bar.dart'; -export 'widgets/add_search_result_tile.dart'; -export 'widgets/episode_tile.dart'; -export 'widgets/search_result_tile.dart'; -export 'widgets/releases_hide_button.dart'; -export 'widgets/releases_search_bar.dart'; -export 'widgets/releases_sorting_button.dart'; -export 'widgets/navigation_bar.dart'; -export 'widgets/series_navigation_bar.dart'; diff --git a/lib/modules/sonarr/widgets/add_search_bar.dart b/lib/modules/sonarr/widgets/add_search_bar.dart deleted file mode 100644 index 10c9a8e6..00000000 --- a/lib/modules/sonarr/widgets/add_search_bar.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrAddSearchBar extends StatefulWidget { - final Function callback; - - SonarrAddSearchBar({ - @required this.callback, - }); - - @override - State createState() => _State(); -} - -class _State extends State { - final TextEditingController _controller = TextEditingController(); - - @override - void initState() { - super.initState(); - final model = Provider.of(context, listen: false); - _controller.text = model.addSearchQuery; - } - - @override - Widget build(BuildContext context) => Expanded( - child: Consumer( - builder: (context, model, widget) => LSTextInputBar( - autofocus: true, - controller: _controller, - onChanged: (text, updateController) => _onChange(model, text, updateController), - onSubmitted: (_) => _onSubmit(), - margin: EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 12.0), - ), - ), - ); - - void _onChange(SonarrModel model, String text, updateController) { - model.addSearchQuery = text; - if(updateController) _controller.text = text; - } - - void _onSubmit() => widget.callback(); -} diff --git a/lib/modules/sonarr/widgets/add_search_result_tile.dart b/lib/modules/sonarr/widgets/add_search_result_tile.dart deleted file mode 100644 index d0ac94dc..00000000 --- a/lib/modules/sonarr/widgets/add_search_result_tile.dart +++ /dev/null @@ -1,59 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrAddSearchResultTile extends StatelessWidget { - final bool alreadyAdded; - final SonarrSearchData data; - - SonarrAddSearchResultTile({ - @required this.alreadyAdded, - @required this.data, - }); - - @override - Widget build(BuildContext context) => LSCardTile( - title: LSTitle(text: data.title, darken: alreadyAdded), - subtitle: RichText( - text: TextSpan( - style: TextStyle( - color: alreadyAdded ? Colors.white30 : Colors.white70, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - ), - children: [ - TextSpan(text: data.year.toString()), - TextSpan(text: ' (${data.status.lsLanguage_Capitalize()})'), - TextSpan(text: '\t•\t${data.seasonCountString}'), - TextSpan(text: '\n${data.overview.trim()}'), - ] - ), - maxLines: 2, - overflow: TextOverflow.fade, - softWrap: false, - ), - trailing: alreadyAdded - ? null - : LSIconButton(icon: Icons.arrow_forward_ios), - onTap: alreadyAdded - ? () => _showAlreadyAddedMessage(context) - : () async => _enterDetails(context), - padContent: true, - ); - - Future _showAlreadyAddedMessage(BuildContext context) => LSSnackBar( - context: context, - title: 'Series Already in Sonarr', - message: data.title, - ); - - Future _enterDetails(BuildContext context) async { - final dynamic result = await Navigator.of(context).pushNamed( - SonarrAddDetails.ROUTE_NAME, - arguments: SonarrAddDetailsArguments(data: data), - ); - if(result != null) switch(result[0]) { - case 'series_added': Navigator.of(context).pop(result); break; - default: Logger.warning('SonarrAddSearchResultTile', '_enterDetails', 'Unknown Case: ${result[0]}'); - } - } -} diff --git a/lib/modules/sonarr/widgets/catalogue_hide_button.dart b/lib/modules/sonarr/widgets/catalogue_hide_button.dart deleted file mode 100644 index 0e891a9f..00000000 --- a/lib/modules/sonarr/widgets/catalogue_hide_button.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrCatalogueHideButton extends StatefulWidget { - final ScrollController controller; - - SonarrCatalogueHideButton({ - Key key, - @required this.controller, - }): super(key: key); - - @override - State createState() => _State(); -} - -class _State extends State { - @override - Widget build(BuildContext context) => LSCard( - child: Consumer( - builder: (context, model, widget) => InkWell( - child: LSIconButton( - icon: model.hideUnmonitoredSeries - ? Icons.visibility_off - : Icons.visibility, - ), - onTap: () => model.hideUnmonitoredSeries = !model.hideUnmonitoredSeries, - borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), - ), - ), - margin: EdgeInsets.fromLTRB(0.0, 0.0, 12.0, 12.0), - color: Theme.of(context).canvasColor, - ); -} diff --git a/lib/modules/sonarr/widgets/catalogue_search_bar.dart b/lib/modules/sonarr/widgets/catalogue_search_bar.dart deleted file mode 100644 index 2d5b7dfa..00000000 --- a/lib/modules/sonarr/widgets/catalogue_search_bar.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrCatalogueSearchBar extends StatefulWidget { - final String prefill; - - SonarrCatalogueSearchBar({ - Key key, - this.prefill = '', - }): super(key: key); - - @override - State createState() => _State(); -} - -class _State extends State { - final _textController = TextEditingController(); - - void initState() { - super.initState(); - _textController.text = widget.prefill ?? ''; - } - - @override - Widget build(BuildContext context) => Expanded( - child: Consumer( - builder: (context, model, widget) => LSTextInputBar( - controller: _textController, - labelText: 'Search Series...', - onChanged: (text, update) => _onChanged(model, text, update), - margin: EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 12.0), - ), - ), - ); - - void _onChanged(SonarrModel model, String text, bool update) { - model.searchCatalogueFilter = text; - if(update) _textController.text = ''; - } -} diff --git a/lib/modules/sonarr/widgets/catalogue_tile.dart b/lib/modules/sonarr/widgets/catalogue_tile.dart deleted file mode 100644 index bd52c076..00000000 --- a/lib/modules/sonarr/widgets/catalogue_tile.dart +++ /dev/null @@ -1,157 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrCatalogueTile extends StatefulWidget { - final SonarrCatalogueData data; - final GlobalKey scaffoldKey; - final Function refresh; - final Function refreshState; - - SonarrCatalogueTile({ - @required this.data, - @required this.scaffoldKey, - @required this.refresh, - @required this.refreshState, - }); - - @override - State createState() => _State(); -} - -class _State extends State { - @override - Widget build(BuildContext context) => LSCardTile( - title: LSTitle( - text: widget.data.title, - darken: !widget.data.monitored, - ), - subtitle: Selector( - selector: (_, model) => model.sortCatalogueType, - builder: (context, type, _) => LSSubtitle( - text: widget.data.subtitle(type), - darken: !widget.data.monitored, - maxLines: 2, - ), - ), - trailing: LSIconButton( - icon: widget.data.monitored - ? Icons.turned_in - : Icons.turned_in_not, - color: widget.data.monitored - ? Colors.white - : Colors.white30, - onPressed: () => _toggleMonitoredStatus(), - ), - padContent: true, - decoration: LSCardBackground( - uri: widget.data.bannerURI(), - darken: !widget.data.monitored, - headers: Database.currentProfileObject.getSonarr()['headers'], - ), - onTap: () async => _enterSeries(), - onLongPress: () async => _handlePopup(), - ); - - Future _toggleMonitoredStatus() async { - final _api = SonarrAPI.from(Database.currentProfileObject); - await _api.toggleSeriesMonitored(widget.data.seriesID, !widget.data.monitored) - .then((_) { - if(mounted) setState(() => widget.data.monitored = !widget.data.monitored); - widget.refreshState(); - LSSnackBar( - context: context, - title: widget.data.monitored ? 'Monitoring' : 'No Longer Monitoring', - message: widget.data.title, - type: SNACKBAR_TYPE.success, - ); - }) - .catchError((_) { - LSSnackBar( - context: context, - title: widget.data.monitored ? 'Failed to Stop Monitoring' : 'Failed to Monitor', - message: Constants.CHECK_LOGS_MESSAGE, - type: SNACKBAR_TYPE.failure, - ); - }); - } - - Future _enterSeries() async { - final dynamic result = await Navigator.of(context).pushNamed( - SonarrDetailsSeries.ROUTE_NAME, - arguments: SonarrDetailsSeriesArguments( - data: widget.data, - seriesID: widget.data.seriesID, - ), - ); - if(result != null) switch(result[0]) { - case 'remove_series': { - LSSnackBar( - context: context, - title: result[1] ? 'Removed (With Data)' : 'Removed', - message: widget.data.title, - type: SNACKBAR_TYPE.success, - ); - widget.refresh(); - break; - } - default: Logger.warning('SonarrCatalogueTile', '_enterSeries', 'Unknown Case: ${result[0]}'); - } - } - - Future _handlePopup() async { - List values = await SonarrDialogs.editSeries(context, widget.data); - if(values[0]) switch(values[1]) { - case 'refresh_series': _refreshSeries(); break; - case 'edit_series': _editSeries(); break; - case 'remove_series': _removeSeries(); break; - default: Logger.warning('SonarrCatalogueTile', '_handlePopup', 'Unknown Case: (${values[1]})'); - } - } - - Future _refreshSeries() async { - SonarrAPI _api = SonarrAPI.from(Database.currentProfileObject); - await _api.refreshSeries(widget.data.seriesID) - .then((_) => LSSnackBar(context: context, title: 'Refreshing...', message: widget.data.title)) - .catchError((_) => LSSnackBar(context: context, title: 'Failed to Refresh', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure)); - } - - Future _editSeries() async { - final dynamic result = await Navigator.of(context).pushNamed( - SonarrEditSeries.ROUTE_NAME, - arguments: SonarrEditSeriesArguments( - data: widget.data, - ), - ); - if(result != null && result[0]) LSSnackBar( - context: context, - title: 'Updated', - message: widget.data.title, - type: SNACKBAR_TYPE.success, - ); - } - Future _removeSeries() async { - final _api = SonarrAPI.from(Database.currentProfileObject); - List values = await SonarrDialogs.deleteSeries(context); - if(values[0]) { - if(values[1]) { - values = await GlobalDialogs.deleteCatalogueWithFiles(context, widget.data.title); - if(values[0]) { - await _api.removeSeries(widget.data.seriesID, deleteFiles: true) - .then((_) { - LSSnackBar(context: context, title: 'Removed (With Data)', message: widget.data.title, type: SNACKBAR_TYPE.success); - widget.refresh(); - }) - .catchError((_) => LSSnackBar(context: context, title: 'Failed to Remove (With Data)', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure)); - } - } else { - await _api.removeSeries(widget.data.seriesID, deleteFiles: false) - .then((_) { - LSSnackBar(context: context, title: 'Removed', message: widget.data.title, type: SNACKBAR_TYPE.success); - widget.refresh(); - }) - .catchError((_) => LSSnackBar(context: context, title: 'Failed to Remove', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure)); - } - } - } -} \ No newline at end of file diff --git a/lib/modules/sonarr/widgets/details_edit_button.dart b/lib/modules/sonarr/widgets/details_edit_button.dart deleted file mode 100644 index ca7725b2..00000000 --- a/lib/modules/sonarr/widgets/details_edit_button.dart +++ /dev/null @@ -1,77 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrDetailsEditButton extends StatefulWidget { - final SonarrCatalogueData data; - final Function(bool) remove; - - SonarrDetailsEditButton({ - @required this.data, - @required this.remove, - }); - - @override - State createState() => _State(); -} - -class _State extends State { - @override - Widget build(BuildContext context) => Consumer( - builder: (context, model, widget) => LSIconButton( - icon: Icons.edit, - onPressed: () async => _handlePopup(context), - ), - ); - - Future _handlePopup(BuildContext context) async { - List values = await SonarrDialogs.editSeries(context, widget.data); - if(values[0]) switch(values[1]) { - case 'refresh_series': _refreshSeries(context); break; - case 'edit_series': _editSeries(context); break; - case 'remove_series': _removeSeries(context); break; - default: Logger.warning('SonarrDetailsEditButton', '_handlePopup', 'Unknown Case: (${values[1]})'); - } - } - - Future _editSeries(BuildContext context) async { - final dynamic result = await Navigator.of(context).pushNamed( - SonarrEditSeries.ROUTE_NAME, - arguments: SonarrEditSeriesArguments( - data: widget.data, - ), - ); - if(result != null && result[0]) LSSnackBar( - context: context, - title: 'Updated', - message: widget.data.title, - type: SNACKBAR_TYPE.success, - ); - } - - Future _refreshSeries(BuildContext context) async { - SonarrAPI _api = SonarrAPI.from(Database.currentProfileObject); - await _api.refreshSeries(widget.data.seriesID) - .then((_) => LSSnackBar(context: context, title: 'Refreshing...', message: widget.data.title)) - .catchError((_) => LSSnackBar(context: context, title: 'Failed to Refresh', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure)); - } - - Future _removeSeries(BuildContext context) async { - final _api = SonarrAPI.from(Database.currentProfileObject); - List values = await SonarrDialogs.deleteSeries(context); - if(values[0]) { - if(values[1]) { - values = await GlobalDialogs.deleteCatalogueWithFiles(context, widget.data.title); - if(values[0]) { - await _api.removeSeries(widget.data.seriesID, deleteFiles: true) - .then((_) => widget.remove(true)) - .catchError((_) => LSSnackBar(context: context, title: 'Failed to Remove (With Data)', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure)); - } - } else { - await _api.removeSeries(widget.data.seriesID, deleteFiles: false) - .then((_) => widget.remove(false)) - .catchError((_) => LSSnackBar(context: context, title: 'Failed to Remove', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure)); - } - } - } -} diff --git a/lib/modules/sonarr/widgets/details_overview.dart b/lib/modules/sonarr/widgets/details_overview.dart deleted file mode 100644 index ff55b540..00000000 --- a/lib/modules/sonarr/widgets/details_overview.dart +++ /dev/null @@ -1,149 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrDetailsOverview extends StatefulWidget { - final SonarrCatalogueData data; - - SonarrDetailsOverview({ - Key key, - @required this.data, - }) : super(key: key); - - @override - State createState() => _State(); -} - -class _State extends State with AutomaticKeepAliveClientMixin { - @override - bool get wantKeepAlive => true; - - @override - Widget build(BuildContext context) { - super.build(context); - return LSListView( - children: [ - LSDescriptionBlock( - title: widget?.data?.title ?? 'Unknown', - description: widget?.data?.overview == '' - ? 'No summary is available.' - : widget?.data?.overview, - uri: widget?.data?.posterURI() ?? '', - fallbackImage: 'assets/images/sonarr/noseriesposter.png', - headers: Database.currentProfileObject.getSonarr()['headers'], - ), - LSCardTile( - title: LSTitle(text: 'Series Path', centerText: true), - subtitle: LSSubtitle(text: widget?.data?.path ?? 'Unknown', centerText: true), - onTap: () => GlobalDialogs.textPreview(context, 'Series Path', widget?.data?.path ?? 'Unknown'), - ), - LSContainerRow( - children: [ - Expanded( - child: LSCardTile( - title: LSTitle(text: 'Quality Profile', centerText: true), - subtitle: LSSubtitle(text: widget?.data?.profile ?? 'Unknown', centerText: true), - reducedMargin: true, - ), - ), - Expanded( - child: LSCardTile( - title: LSTitle(text: 'Series Type', centerText: true), - subtitle: LSSubtitle(text: widget?.data?.type?.lsLanguage_Capitalize() ?? 'Unknown', centerText: true), - reducedMargin: true, - ), - ), - ], - ), - LSContainerRow( - children: [ - Expanded( - child: LSCardTile( - title: LSTitle(text: 'Network', centerText: true), - subtitle: LSSubtitle(text: widget?.data?.network ?? 'None', centerText: true), - reducedMargin: true, - ), - ), - Expanded( - child: LSCardTile( - title: LSTitle(text: 'Runtime', centerText: true), - subtitle: LSSubtitle(text: widget.data.runtime > 0 ? '${widget.data.runtime} Minutes' : 'Unknown', centerText: true), - reducedMargin: true, - ), - ), - ], - ), - LSContainerRow( - children: [ - Expanded( - child: LSCardTile( - title: LSTitle(text: 'Next Air Date', centerText: true), - subtitle: LSSubtitle(text: widget.data.status == 'ended' ? 'Series Ended' : widget.data.nextEpisode ?? 'Unknown', centerText: true), - reducedMargin: true, - ), - ), - Expanded( - child: LSCardTile( - title: LSTitle(text: 'Air Time', centerText: true), - subtitle: LSSubtitle(text: widget.data.airTimeString ?? 'Unknown', centerText: true), - reducedMargin: true, - ), - ), - ], - ), - LSContainerRow( - children: [ - if(widget.data.imdbId != '') Expanded( - child: LSCard( - child: InkWell( - child: Padding( - child: Image.asset( - 'assets/images/services/imdb.png', - height: 21.0, - ), - padding: EdgeInsets.all(18.0), - ), - borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), - onTap: () async => await widget.data?.imdbId?.lsLinks_OpenIMDB(), - ), - reducedMargin: true, - ), - ), - if(widget.data.tvdbId != 0) Expanded( - child: LSCard( - child: InkWell( - child: Padding( - child: Image.asset( - 'assets/images/services/thetvdb.png', - height: 21.0, - ), - padding: EdgeInsets.all(18.0), - ), - borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), - onTap: () async => await widget.data?.tvdbId?.toString()?.lsLinks_OpenTVDB(), - ), - reducedMargin: true, - ), - ), - if(widget.data.tvMazeId != 0) Expanded( - child: LSCard( - child: InkWell( - child: Padding( - child: Image.asset( - 'assets/images/services/tvmaze.png', - height: 21.0, - ), - padding: EdgeInsets.all(18.0), - ), - borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), - onTap: () async => await widget.data?.tvMazeId?.toString()?.lsLinks_OpenTVMaze(), - ), - reducedMargin: true, - ), - ), - ], - ), - ], - ); - } -} diff --git a/lib/modules/sonarr/widgets/details_season_list.dart b/lib/modules/sonarr/widgets/details_season_list.dart deleted file mode 100644 index 7b43ab14..00000000 --- a/lib/modules/sonarr/widgets/details_season_list.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrDetailsSeasonList extends StatefulWidget { - final SonarrCatalogueData data; - - SonarrDetailsSeasonList({ - Key key, - @required this.data, - }) : super(key: key); - - @override - State createState() => _State(); -} - -class _State extends State with AutomaticKeepAliveClientMixin { - @override - bool get wantKeepAlive => true; - - @override - Widget build(BuildContext context) { - super.build(context); - return widget.data.seasonData.length == 0 - ? _empty - : _list; - } - - Widget get _list => LSListViewBuilder( - itemCount: widget.data.seasonData.length+1, - itemBuilder: (context, index) => SonarrDetailsSeasonListTile( - data: widget.data, - index: index == 0 ? -1 : widget.data.seasonData.length-index, - ), - ); - - Widget get _empty => LSListView( - children: [LSGenericMessage(text: 'No Seasons Found')], - ); -} diff --git a/lib/modules/sonarr/widgets/details_season_list_tile.dart b/lib/modules/sonarr/widgets/details_season_list_tile.dart deleted file mode 100644 index 1d2d5897..00000000 --- a/lib/modules/sonarr/widgets/details_season_list_tile.dart +++ /dev/null @@ -1,159 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrDetailsSeasonListTile extends StatefulWidget { - final SonarrCatalogueData data; - final int index; - - SonarrDetailsSeasonListTile({ - Key key, - @required this.data, - @required this.index, - }): super(key: key); - - @override - State createState() => _State(); -} - -class _State extends State { - @override - Widget build(BuildContext context) => widget.index == -1 - ? _allSeasons - : _season; - - Widget get _allSeasons { - return LSCardTile( - title: LSTitle(text: 'All Seasons', darken: !widget.data.monitored), - subtitle: RichText( - text: TextSpan( - style: TextStyle( - color: widget.data.monitored - ? Colors.white70 - : Colors.white30, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - ), - children: [ - TextSpan(text: '${widget.data.episodeFileCount ?? 0}/${widget.data.episodeCount ?? 0} Episodes Available\n'), - TextSpan( - text: '${widget.data.percentageComplete}% Complete', - style: TextStyle( - fontWeight: FontWeight.bold, - color: widget.data.monitored - ? widget.data.percentageComplete == 100 - ? LSColors.accent - : Colors.red - : LSColors.orange.withOpacity(0.30), - ), - ), - ], - ), - ), - trailing: LSIconButton( - icon: Icons.arrow_forward_ios, - color: widget.data.monitored - ? Colors.white - : Colors.white30, - ), - onTap: () => _enterSeason(-1), - padContent: true, - ); - } - - Widget get _season { - Map _seasonData = widget.data.seasonData[widget.index]; - int episodeCount = 0; - int availableEpisodeCount = 0; - if(_seasonData['statistics'] != null) { - episodeCount = _seasonData['statistics']['totalEpisodeCount'] ?? 0; - availableEpisodeCount = _seasonData['statistics']['episodeFileCount'] ?? 0; - } - int percentage = episodeCount == 0 - ? 0 - : ((availableEpisodeCount/episodeCount)*100).round(); - return LSCardTile( - title: LSTitle( - text: _seasonData['seasonNumber'] == 0 - ? 'Specials' - : 'Season ${_seasonData['seasonNumber']}', - darken: !_seasonData['monitored'], - ), - subtitle: RichText( - text: TextSpan( - style: TextStyle( - color: _seasonData['monitored'] - ? Colors.white70 - : Colors.white30, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - ), - children: [ - TextSpan(text: '$availableEpisodeCount/$episodeCount Episodes Available\n'), - TextSpan( - text: '$percentage% Complete', - style: TextStyle( - fontWeight: FontWeight.bold, - color: _seasonData['monitored'] - ? percentage == 100 - ? LSColors.accent - : Colors.red - : LSColors.orange.withOpacity(0.30), - ), - ), - ], - ), - ), - trailing: LSIconButton( - icon: _seasonData['monitored'] - ? Icons.turned_in - : Icons.turned_in_not, - color: _seasonData['monitored'] - ? Colors.white - : Colors.white30, - onPressed: () async => _toggleMonitorStatus(), - ), - onTap: () async => _enterSeason(_seasonData['seasonNumber']), - onLongPress: () async => _searchSeason(_seasonData['seasonNumber']), - padContent: true, - ); - } - - Future _toggleMonitorStatus() async { - SonarrAPI _api = SonarrAPI.from(Database.currentProfileObject); - await _api.toggleSeasonMonitored(widget.data.seriesID, widget.data.seasonData[widget.index]['seasonNumber'], !widget.data.seasonData[widget.index]['monitored']) - .then((_) { - if(mounted) setState(() => widget.data.seasonData[widget.index]['monitored'] = !widget.data.seasonData[widget.index]['monitored']); - LSSnackBar(context: context, title: widget.data.seasonData[widget.index]['monitored'] ? 'Monitoring' : 'No Longer Monitoring', message: widget.data.seasonData[widget.index]['seasonNumber'] == 0 ? 'Specials' : 'Season ${widget.data.seasonData[widget.index]['seasonNumber']}', type: SNACKBAR_TYPE.success); - }) - .catchError((_) => LSSnackBar(context: context, title: widget.data.seasonData[widget.index]['monitored'] ? 'Failed to Stop Monitoring' : 'Failed to Monitor', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure)); - } - - Future _enterSeason(int season) async => await Navigator.of(context).pushNamed( - SonarrDetailsSeason.ROUTE_NAME, - arguments: SonarrDetailsSeasonArguments( - season: season, - title: widget.data.title, - seriesID: widget.data.seriesID, - ), - ); - - Future _searchSeason(int season) async { - List _values = await SonarrDialogs.searchEntireSeason(context, season); - if(_values[0]) { - SonarrAPI _api = SonarrAPI.from(Database.currentProfileObject); - await _api.searchSeason(widget.data.seriesID, season) - .then((_) => LSSnackBar( - context: context, - title: 'Searching...', - message: season == 0 - ? 'Searching for all episodes in specials' - : 'Searching for all episodes in season $season', - )) - .catchError((_) => LSSnackBar( - context: context, - title: 'Failed to Search', - message: Constants.CHECK_LOGS_MESSAGE, - type: SNACKBAR_TYPE.failure, - )); - } - } -} diff --git a/lib/modules/sonarr/widgets/episode_tile.dart b/lib/modules/sonarr/widgets/episode_tile.dart deleted file mode 100644 index 8fe5d32e..00000000 --- a/lib/modules/sonarr/widgets/episode_tile.dart +++ /dev/null @@ -1,277 +0,0 @@ -import 'package:expandable/expandable.dart'; -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrEpisodeTile extends StatefulWidget { - final SonarrEpisodeData data; - final Function(bool, int) selectedCallback; - - SonarrEpisodeTile({ - @required this.data, - @required this.selectedCallback, - }); - - @override - State createState() => _State(); -} - -class _State extends State { - final ExpandableController _controller = ExpandableController(); - - @override - Widget build(BuildContext context) => LSExpandable( - controller: _controller, - collapsed: _collapsed(context), - expanded: _expanded(context), - ); - - Widget _expanded(BuildContext context) => LSCard( - child: InkWell( - child: Row( - children: [ - Expanded( - child: Padding( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LSTitle( - text: widget.data.episodeTitle, - softWrap: true, - maxLines: 12, - ), - Padding( - child: Wrap( - direction: Axis.horizontal, - runSpacing: 10.0, - children: [ - if(!widget.data.isMonitored) LSTextHighlighted( - text: 'Unmonitored', - bgColor: LSColors.red, - ), - widget.data.subtitle(asHighlight: true), - ], - ), - padding: EdgeInsets.only(top: 8.0, bottom: 2.0), - ), - Padding( - child: RichText( - text: TextSpan( - style: TextStyle( - color: Colors.white70, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - ), - children: [ - TextSpan( - text: widget.data.seasonNumber == 0 - ? 'Specials / Episode ${widget.data.episodeNumber}\n' - : 'Season ${widget.data.seasonNumber} / Episode ${widget.data.episodeNumber}\n', - style: TextStyle( - color: LSColors.accent, - fontWeight: FontWeight.bold, - fontSize: Constants.UI_FONT_SIZE_STICKYHEADER, - ), - ), - TextSpan( - text: '${widget.data.airDateString}\n\n', - style: TextStyle( - color: Colors.white, - ), - ), - TextSpan( - text: widget.data.overview, - style: TextStyle( - fontStyle: FontStyle.italic, - ), - ) - ], - ), - ), - padding: EdgeInsets.only(top: 6.0, bottom: 10.0), - ), - Padding( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: LSButtonSlim( - text: 'Automatic', - onTap: () => _automaticSearch(), - margin: EdgeInsets.only(right: 6.0), - ), - ), - Expanded( - child: LSButtonSlim( - text: 'Interactive', - backgroundColor: LSColors.orange, - onTap: () => _manualSearch(), - margin: EdgeInsets.only(left: 6.0), - ), - ), - ], - ), - padding: EdgeInsets.only(bottom: 2.0), - ), - ], - ), - padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 10.0), - ), - ), - ], - ), - borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), - onTap: () => _controller.toggle(), - onLongPress: () => _handlePopup(), - ), - color: widget.data.isSelected - ? LSColors.accent.withOpacity(0.25) - : null, - ); - - Widget _collapsed(BuildContext context) => LSCardTile( - title: LSTitle( - text: widget.data.episodeTitle, - darken: !widget.data.isMonitored, - ), - subtitle: RichText( - text: TextSpan( - style: TextStyle( - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - ), - children: [ - TextSpan( - text: '${widget.data.airDateString}\n', - style: TextStyle( - color: widget.data.isMonitored - ? Colors.white70 - : Colors.white30, - ), - ), - (widget.data.subtitle() as TextSpan) - ], - ), - ), - leading: IconButton( - icon: widget.data.isSelected - ? LSIcon(icon: Icons.check) - : Text( - '${widget.data.episodeNumber}', - textAlign: TextAlign.center, - style: TextStyle( - color: widget.data.isMonitored ? Colors.white : Colors.white30, - fontWeight: FontWeight.bold, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - ), - ), - onPressed: () => _handleSelected(), - ), - decoration: widget.data.isSelected - ? BoxDecoration( - color: LSColors.accent.withOpacity(0.25), - borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), - ) - : null, - trailing: InkWell( - child: LSIconButton( - icon: Icons.search, - color: widget.data.isMonitored - ? Colors.white - : Colors.white30, - onPressed: () async => _automaticSearch(), - ), - onLongPress: () async => _manualSearch(), - ), - onTap: () => _controller.toggle(), - onLongPress: () => _handlePopup(), - padContent: true, - ); - - void _handleSelected() { - setState(() => widget.data.isSelected = !widget.data.isSelected); - widget.selectedCallback(widget.data.isSelected, widget.data.episodeID); - } - - Future _automaticSearch() async { - SonarrAPI _api = SonarrAPI.from(Database.currentProfileObject); - return await _api.searchEpisodes([widget.data.episodeID]) - .then((_) => LSSnackBar( - context: context, - title: 'Searching...', - message: widget.data.episodeTitle, - )) - .catchError((_) => LSSnackBar( - context: context, - title: 'Failed to Search', - message: Constants.CHECK_LOGS_MESSAGE, - type: SNACKBAR_TYPE.failure, - )); - } - - Future _manualSearch() async => Navigator.of(context).pushNamed( - SonarrSearchResults.ROUTE_NAME, - arguments: SonarrSearchResultsArguments( - episodeID: widget.data.episodeID, - title: widget.data.episodeTitle, - ), - ); - - Future _deleteFile() async { - List _values = await SonarrDialogs.deleteEpisodeFile(context); - if(_values[0]) { - SonarrAPI _api = SonarrAPI.from(Database.currentProfileObject); - await _api.deleteEpisodeFile(widget.data.episodeFileID) - .then((_) { - LSSnackBar( - context: context, - title: 'Deleted Episode File', - message: widget.data.episodeTitle, - type: SNACKBAR_TYPE.success, - ); - if(mounted) setState(() { - widget.data.hasFile = false; - widget.data.isMonitored = false; - widget.data.episodeFileID = 0; - }); - }) - .catchError((_) => LSSnackBar( - context: context, - title: 'Failed to Delete Episode File', - message: Constants.CHECK_LOGS_MESSAGE, - type: SNACKBAR_TYPE.failure, - )); - } - } - - Future _toggleMonitoredStatus() async { - SonarrAPI _api = SonarrAPI.from(Database.currentProfileObject); - await _api.toggleEpisodeMonitored(widget.data.episodeID, !widget.data.isMonitored) - .then((_) { - LSSnackBar( - context: context, - title: widget.data.isMonitored ? 'No Longer Monitoring' : 'Monitoring', - message: widget.data.episodeTitle, - type: SNACKBAR_TYPE.success, - ); - if(mounted) setState(() { - widget.data.isMonitored = !widget.data.isMonitored; - }); - }) - .catchError((_) => LSSnackBar( - context: context, - title: widget.data.isMonitored ? 'Failed to Stop Monitoring' : 'Failed to Monitor', - message: Constants.CHECK_LOGS_MESSAGE, - type: SNACKBAR_TYPE.failure, - )); - } - - Future _handlePopup() async { - List _values = await SonarrDialogs.editEpisode(context, widget.data.episodeTitle, widget.data.isMonitored, widget.data.hasFile); - if(_values[0]) switch(_values[1]) { - case 'monitor_status': _toggleMonitoredStatus().catchError((_) {}); break; - case 'search_automatic': await _automaticSearch().catchError((_) {}); break; - case 'search_manual': await _manualSearch().catchError((_) {}); break; - case 'delete_file': _deleteFile().catchError((_) {}); break; - default: Logger.warning('SonarrEpisodeTile', '_handlePopup', 'Unkown Case: ${_values[1]}'); - } - } -} diff --git a/lib/modules/sonarr/widgets/history_tile.dart b/lib/modules/sonarr/widgets/history_tile.dart deleted file mode 100644 index 6d00effd..00000000 --- a/lib/modules/sonarr/widgets/history_tile.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrHistoryTile extends StatefulWidget { - final SonarrHistoryData data; - final GlobalKey scaffoldKey; - final Function refresh; - - SonarrHistoryTile({ - @required this.data, - @required this.scaffoldKey, - @required this.refresh, - }); - - @override - State createState() => _State(); -} - -class _State extends State { - @override - Widget build(BuildContext context) => LSCardTile( - title: LSTitle(text: widget.data.seriesTitle), - subtitle: RichText( - text: TextSpan( - style: TextStyle( - color: Colors.white70, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - ), - children: widget.data.subtitle, - ), - ), - trailing: LSIconButton( - icon: Icons.arrow_forward_ios, - ), - padContent: true, - onTap: () async => _enterSeries(), - ); - - Future _enterSeries() async { - final dynamic result = await Navigator.of(context).pushNamed( - SonarrDetailsSeries.ROUTE_NAME, - arguments: SonarrDetailsSeriesArguments( - data: null, - seriesID: widget.data.seriesID, - ), - ); - if(result != null) switch(result[0]) { - case 'remove_series': { - LSSnackBar( - context: context, - title: result[1] ? 'Removed (With Data)' : 'Removed', - message: widget.data.seriesTitle, - type: SNACKBAR_TYPE.success, - ); - widget.refresh(); - break; - } - default: Logger.warning('SonarrHistoryTile', '_enterSeries', 'Unknown Case: ${result[0]}'); - } - } -} diff --git a/lib/modules/sonarr/widgets/missing_tile.dart b/lib/modules/sonarr/widgets/missing_tile.dart deleted file mode 100644 index ab11c10a..00000000 --- a/lib/modules/sonarr/widgets/missing_tile.dart +++ /dev/null @@ -1,117 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrMissingTile extends StatefulWidget { - final SonarrMissingData data; - final GlobalKey scaffoldKey; - final Function refresh; - - SonarrMissingTile({ - @required this.data, - @required this.scaffoldKey, - @required this.refresh, - }); - - @override - State createState() => _State(); -} - -class _State extends State { - @override - Widget build(BuildContext context) => LSCardTile( - title: LSTitle(text: widget.data.showTitle), - subtitle: RichText( - text: TextSpan( - style: TextStyle( - color: Colors.white70, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - ), - children: [ - TextSpan( - text: widget.data.seasonEpisode, - style: TextStyle( - color: Colors.white70, - fontStyle: FontStyle.italic, - ), - ), - TextSpan( - text: ': ${widget.data.episodeTitle}\n', - style: TextStyle( - fontStyle: FontStyle.italic, - ), - ), - TextSpan( - text: 'Aired ${widget.data.airDateString}', - style: TextStyle( - color: Colors.red, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - overflow: TextOverflow.fade, - softWrap: false, - maxLines: 2, - ), - trailing: LSIconButton( - icon: Icons.search, - onPressed: () async => _search(), - onLongPress: () async => _interactiveSearch(), - ), - onTap: () async => _enterSeason(), - onLongPress: () async => _enterSeries(), - decoration: LSCardBackground( - uri: widget.data.bannerURI(), - headers: Database.currentProfileObject.getSonarr()['headers'], - ), - padContent: true, - ); - - Future _search() async { - final _api = SonarrAPI.from(Database.currentProfileObject); - await _api.searchEpisodes([widget.data.episodeID]) - .then((_) => LSSnackBar(context: context, title: 'Searching...', message: widget.data.episodeTitle)) - .catchError((_) => LSSnackBar(context: context, title: 'Failed to Search', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure)); - } - - Future _interactiveSearch() async => Navigator.of(context).pushNamed( - SonarrSearchResults.ROUTE_NAME, - arguments: SonarrSearchResultsArguments( - episodeID: widget.data.episodeID, - title: widget.data.episodeTitle, - ), - ); - - Future _enterSeason() async => await Navigator.of(context).pushNamed( - SonarrDetailsSeason.ROUTE_NAME, - arguments: SonarrDetailsSeasonArguments( - season: widget.data.seasonNumber, - title: widget.data.showTitle, - seriesID: widget.data.seriesID, - ), - ); - - Future _enterSeries() async { - final dynamic result = await Navigator.of(context).pushNamed( - SonarrDetailsSeries.ROUTE_NAME, - arguments: SonarrDetailsSeriesArguments( - data: null, - seriesID: widget.data.seriesID, - ), - ); - if(result != null) switch(result[0]) { - case 'remove_series': { - LSSnackBar( - context: context, - title: result[1] ? 'Removed (With Data)' : 'Removed', - message: widget.data.showTitle, - type: SNACKBAR_TYPE.success, - ); - widget.refresh(); - break; - } - default: Logger.warning('SonarrMissingTile', '_enterSeries', 'Unknown Case: ${result[0]}'); - } - } -} \ No newline at end of file diff --git a/lib/modules/sonarr/widgets/releases_hide_button.dart b/lib/modules/sonarr/widgets/releases_hide_button.dart deleted file mode 100644 index 5bb63277..00000000 --- a/lib/modules/sonarr/widgets/releases_hide_button.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrReleasesHideButton extends StatefulWidget { - final ScrollController controller; - - SonarrReleasesHideButton({ - Key key, - @required this.controller, - }): super(key: key); - - @override - State createState() => _State(); -} - -class _State extends State { - @override - Widget build(BuildContext context) => LSCard( - child: Consumer( - builder: (context, model, widget) => InkWell( - child: LSIconButton( - icon: model.hideRejectedReleases - ? Icons.visibility_off - : Icons.visibility, - ), - onTap: () => model.hideRejectedReleases = !model.hideRejectedReleases, - borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), - ), - ), - margin: EdgeInsets.fromLTRB(0.0, 0.0, 12.0, 12.0), - color: Theme.of(context).canvasColor, - ); -} diff --git a/lib/modules/sonarr/widgets/releases_search_bar.dart b/lib/modules/sonarr/widgets/releases_search_bar.dart deleted file mode 100644 index 2e12dc4a..00000000 --- a/lib/modules/sonarr/widgets/releases_search_bar.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrReleasesSearchBar extends StatefulWidget { - final String prefill; - - SonarrReleasesSearchBar({ - Key key, - this.prefill = '', - }): super(key: key); - - @override - State createState() => _State(); -} - -class _State extends State { - final _textController = TextEditingController(); - - void initState() { - super.initState(); - _textController.text = widget.prefill ?? ''; - } - - @override - Widget build(BuildContext context) => Expanded( - child: Consumer( - builder: (context, model, widget) => LSTextInputBar( - controller: _textController, - labelText: 'Search Releases...', - onChanged: (text, update) => _onChanged(model, text, update), - margin: EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 12.0), - ), - ), - ); - - void _onChanged(SonarrModel model, String text, bool update) { - model.searchReleasesFilter = text; - if(update) _textController.text = ''; - } -} diff --git a/lib/modules/sonarr/widgets/series_navigation_bar.dart b/lib/modules/sonarr/widgets/series_navigation_bar.dart deleted file mode 100644 index 6935e93a..00000000 --- a/lib/modules/sonarr/widgets/series_navigation_bar.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrSeriesNavigationBar extends StatefulWidget { - final PageController pageController; - - SonarrSeriesNavigationBar({ - Key key, - @required this.pageController, - }): super(key: key); - - @override - State createState() => _State(); -} - -class _State extends State { - static const List _navbarTitles = [ - 'Overview', - 'Seasons', - ]; - - static const List _navbarIcons = [ - Icons.subject, - CustomIcons.television, - ]; - - @override - Widget build(BuildContext context) => Selector( - selector: (_, model) => model.seriesNavigationIndex, - builder: (context, index, _) => LSBottomNavigationBar( - index: index, - icons: _navbarIcons, - titles: _navbarTitles, - onTap: (index) async => await _navOnTap(index), - ), - ); - - Future _navOnTap(int index) async { - await widget.pageController.animateToPage( - index, - duration: Duration(milliseconds: Constants.UI_NAVIGATION_SPEED), - curve: Curves.easeOutSine, - ).then((_) => Provider.of(context, listen: false).seriesNavigationIndex = index); - } -} diff --git a/lib/modules/sonarr/widgets/upcoming_tile.dart b/lib/modules/sonarr/widgets/upcoming_tile.dart deleted file mode 100644 index ca057080..00000000 --- a/lib/modules/sonarr/widgets/upcoming_tile.dart +++ /dev/null @@ -1,115 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/sonarr.dart'; - -class SonarrUpcomingTile extends StatefulWidget { - final SonarrUpcomingData data; - final GlobalKey scaffoldKey; - final Function refresh; - - SonarrUpcomingTile({ - @required this.data, - @required this.scaffoldKey, - @required this.refresh, - }); - - @override - State createState() => _State(); -} - -class _State extends State { - @override - Widget build(BuildContext context) => LSCardTile( - title: LSTitle(text: widget.data.seriesTitle), - subtitle: RichText( - text: TextSpan( - style: TextStyle( - color: Colors.white70, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - ), - children: [ - TextSpan(text: 'Season ${widget.data.seasonNumber} Episode ${widget.data.episodeNumber}: '), - TextSpan( - text: '${widget.data.episodeTitle}\n', - style: TextStyle( - fontStyle: FontStyle.italic, - ), - ), - widget.data.downloaded, - ], - ), - maxLines: 2, - softWrap: false, - overflow: TextOverflow.fade, - ), - trailing: InkWell( - child: IconButton( - icon: Text( - widget.data.airTimeString, - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: Constants.UI_FONT_SIZE_SUBHEADER-2.0, - ), - ), - onPressed: () async => _search(), - ), - onLongPress: () async => _interactiveSearch(), - ), - onTap: () async => _enterSeason(), - onLongPress: () async => _enterSeries(), - padContent: true, - decoration: LSCardBackground( - uri: widget.data.bannerURI(), - headers: Database.currentProfileObject.getSonarr()['headers'], - ), - ); - - Future _search() async { - final _api = SonarrAPI.from(Database.currentProfileObject); - await _api.searchEpisodes([widget.data.id]) - .then((_) => LSSnackBar(context: context, title: 'Searching...', message: widget.data.episodeTitle)) - .catchError((_) => LSSnackBar(context: context, title: 'Failed to Search', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure)); - } - - Future _interactiveSearch() async => Navigator.of(context).pushNamed( - SonarrSearchResults.ROUTE_NAME, - arguments: SonarrSearchResultsArguments( - episodeID: widget.data.id, - title: widget.data.episodeTitle, - ), - ); - - Future _enterSeason() async => await Navigator.of(context).pushNamed( - SonarrDetailsSeason.ROUTE_NAME, - arguments: SonarrDetailsSeasonArguments( - season: widget.data.seasonNumber, - title: widget.data.seriesTitle, - seriesID: widget.data.seriesID, - ), - ); - - Future _enterSeries() async { - final dynamic result = await Navigator.of(context).pushNamed( - SonarrDetailsSeries.ROUTE_NAME, - arguments: SonarrDetailsSeriesArguments( - data: null, - seriesID: widget.data.seriesID, - ), - ); - if(result != null) switch(result[0]) { - case 'remove_series': { - LSSnackBar( - context: context, - title: result[1] ? 'Removed (With Data)' : 'Removed', - message: widget.data.seriesTitle, - type: SNACKBAR_TYPE.success, - ); - widget.refresh(); - break; - } - default: Logger.warning('SonarrUpcomingTile', '_enterSeries', 'Unknown Case: ${result[0]}'); - } - } -} diff --git a/lib/modules/tautulli.dart b/lib/modules/tautulli.dart index 81d51a6b..b960d394 100644 --- a/lib/modules/tautulli.dart +++ b/lib/modules/tautulli.dart @@ -1,3 +1,3 @@ +export 'package:tautulli/tautulli.dart'; export 'tautulli/core.dart'; -export 'tautulli/main.dart'; export 'tautulli/modules.dart'; diff --git a/lib/modules/tautulli/core/constants.dart b/lib/modules/tautulli/core/constants.dart index ab06b61f..a5480b13 100644 --- a/lib/modules/tautulli/core/constants.dart +++ b/lib/modules/tautulli/core/constants.dart @@ -6,7 +6,7 @@ class TautulliConstants { static const String MODULE_KEY = 'tautulli'; - static const ModuleMap MODULE_MAP = ModuleMap( + static const LunaModuleMap MODULE_MAP = LunaModuleMap( name: 'Tautulli', description: 'View Plex Activity', settingsDescription: 'Configure Tautulli', diff --git a/lib/modules/tautulli/core/database.dart b/lib/modules/tautulli/core/database.dart index f6f4db40..0fcaccde 100644 --- a/lib/modules/tautulli/core/database.dart +++ b/lib/modules/tautulli/core/database.dart @@ -1,4 +1,3 @@ -//import 'package:hive/hive.dart'; import 'package:lunasea/core.dart'; class TautulliDatabase { diff --git a/lib/modules/tautulli/core/dialogs.dart b/lib/modules/tautulli/core/dialogs.dart index 426882ee..2e31f568 100644 --- a/lib/modules/tautulli/core/dialogs.dart +++ b/lib/modules/tautulli/core/dialogs.dart @@ -3,6 +3,8 @@ import 'package:lunasea/core.dart'; import 'package:lunasea/modules/tautulli.dart'; class TautulliDialogs { + TautulliDialogs._(); + static Future> globalSettings(BuildContext context) async { bool _flag = false; TautulliGlobalSettingsType _value; @@ -21,7 +23,7 @@ class TautulliDialogs { (index) => LSDialog.tile( text: TautulliGlobalSettingsType.values[index].name, icon: TautulliGlobalSettingsType.values[index].icon, - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, TautulliGlobalSettingsType.values[index]), ), ), @@ -51,7 +53,7 @@ class TautulliDialogs { (index) => LSDialog.tile( text: titles[index], icon: icons[index], - iconColor: LSColors.list(index), + iconColor: LunaColours.list(index), onTap: () => _setValues(true, index), ), ), @@ -79,7 +81,7 @@ class TautulliDialogs { buttons: [ LSDialog.button( text: 'Terminate', - textColor: LSColors.red, + textColor: LunaColours.red, onPressed: () => _setValues(true), ), ], diff --git a/lib/modules/tautulli/core/extensions/tautulli_session.dart b/lib/modules/tautulli/core/extensions/tautulli_session.dart index a191f495..d7e3a6bf 100644 --- a/lib/modules/tautulli/core/extensions/tautulli_session.dart +++ b/lib/modules/tautulli/core/extensions/tautulli_session.dart @@ -35,7 +35,11 @@ extension TautulliSessionExtension on TautulliSession { switch(this.transcodeDecision) { case TautulliTranscodeDecision.TRANSCODE: String _transcodeStatus = this.transcodeThrottled ? 'Throttled' : '${this.transcodeSpeed ?? 0.0}x'; - return 'Transcode ($_transcodeStatus)'; + return [ + 'Transcode', + if(this.transcodeHardwareFullPipeline) ' (hw)', + ' ($_transcodeStatus)', + ].join(); case TautulliTranscodeDecision.COPY: return 'Direct Stream'; case TautulliTranscodeDecision.DIRECT_PLAY: return 'Direct Play'; case TautulliTranscodeDecision.NULL: @@ -74,13 +78,12 @@ extension TautulliSessionExtension on TautulliSession { ].join(); String lsArtworkPath(BuildContext context) { - TautulliState _state = Provider.of(context, listen: false); switch(this.mediaType) { - case TautulliMediaType.EPISODE: return _state.getImageURLFromRatingKey(this.grandparentRatingKey); - case TautulliMediaType.TRACK: return _state.getImageURLFromRatingKey(this.parentRatingKey); + case TautulliMediaType.EPISODE: return context.watch().getImageURLFromRatingKey(this.grandparentRatingKey); + case TautulliMediaType.TRACK: return context.watch().getImageURLFromRatingKey(this.parentRatingKey); case TautulliMediaType.MOVIE: case TautulliMediaType.LIVE: - default: return _state.getImageURLFromRatingKey(this.ratingKey); + default: return context.watch().getImageURLFromRatingKey(this.ratingKey); } } @@ -100,7 +103,11 @@ extension TautulliSessionExtension on TautulliSession { switch(this.transcodeDecision) { case TautulliTranscodeDecision.TRANSCODE: String _transcodeStatus = this.transcodeThrottled ? 'Throttled' : '${this.transcodeSpeed ?? 0.0}x'; - return 'Transcoding ($_transcodeStatus)'; + return [ + 'Transcode', + if(this.transcodeHardwareFullPipeline) ' (hw)', + ' ($_transcodeStatus)', + ].join(); case TautulliTranscodeDecision.DIRECT_PLAY: case TautulliTranscodeDecision.COPY: case TautulliTranscodeDecision.NULL: diff --git a/lib/modules/tautulli/core/graphs/bar_graph.dart b/lib/modules/tautulli/core/graphs/bar_graph.dart index aaa311f6..5aa571b3 100644 --- a/lib/modules/tautulli/core/graphs/bar_graph.dart +++ b/lib/modules/tautulli/core/graphs/bar_graph.dart @@ -27,7 +27,7 @@ class TautulliBarGraphHelper { (sIndex) => BarChartRodStackItem( _fromY(cIndex, sIndex, data.series), _toY(cIndex, sIndex, data.series), - LSColors.graph(sIndex), + LunaColours.graph(sIndex), ), ), ), @@ -38,7 +38,7 @@ class TautulliBarGraphHelper { static BarTouchData barTouchData(BuildContext context, TautulliGraphData data) => BarTouchData( enabled: true, touchTooltipData: BarTouchTooltipData( - tooltipBgColor: LunaSeaDatabaseValue.THEME_AMOLED.data ? Colors.black : LSColors.primary, + tooltipBgColor: LunaSeaDatabaseValue.THEME_AMOLED.data ? Colors.black : LunaColours.primary, tooltipRoundedRadius: Constants.UI_BORDER_RADIUS, tooltipPadding: EdgeInsets.all(8.0), maxContentWidth: MediaQuery.of(context).size.width/1.25, @@ -50,7 +50,7 @@ class TautulliBarGraphHelper { for(int i=0; i(context, listen: false).graphYAxis == TautulliGraphYAxis.PLAYS + String _text = context.read().graphYAxis == TautulliGraphYAxis.PLAYS ? (_number?.truncate() ?? 0).toString() : Duration(seconds: _number?.truncate() ?? 0).lsDuration_fullTimestamp(); _body += '$_value: $_text\n'; diff --git a/lib/modules/tautulli/core/graphs/graph.dart b/lib/modules/tautulli/core/graphs/graph.dart index 69cb72ce..832137a7 100644 --- a/lib/modules/tautulli/core/graphs/graph.dart +++ b/lib/modules/tautulli/core/graphs/graph.dart @@ -55,7 +55,7 @@ class TautulliGraphHelper { height: Constants.UI_FONT_SIZE_GRAPH_LEGEND, width: Constants.UI_FONT_SIZE_GRAPH_LEGEND, decoration: BoxDecoration( - color: LSColors.graph(index), + color: LunaColours.graph(index), borderRadius: BorderRadius.circular(8.0), ), ), @@ -65,7 +65,7 @@ class TautulliGraphHelper { data[index].name, style: TextStyle( fontSize: Constants.UI_FONT_SIZE_GRAPH_LEGEND, - color: LSColors.graph(index), + color: LunaColours.graph(index), ), ), ], diff --git a/lib/modules/tautulli/core/graphs/line_graph.dart b/lib/modules/tautulli/core/graphs/line_graph.dart index a1611d1a..9834a4f7 100644 --- a/lib/modules/tautulli/core/graphs/line_graph.dart +++ b/lib/modules/tautulli/core/graphs/line_graph.dart @@ -32,14 +32,14 @@ class TautulliLineGraphHelper { isCurved: true, isStrokeCapRound: true, barWidth: 3.0, - colors: [LSColors.graph(sIndex)], + colors: [LunaColours.graph(sIndex)], spots: List.generate( data.series[sIndex].data.length, (dIndex) => FlSpot(dIndex.toDouble(), data.series[sIndex].data[dIndex].toDouble()), ), belowBarData: BarAreaData( show: true, - colors: [LSColors.graph(sIndex).withOpacity(0.20)], + colors: [LunaColours.graph(sIndex).withOpacity(0.20)], ), dotData: FlDotData( show: true, @@ -55,7 +55,7 @@ class TautulliLineGraphHelper { static LineTouchData lineTouchData(BuildContext context, TautulliGraphData data) => LineTouchData( enabled: true, touchTooltipData: LineTouchTooltipData( - tooltipBgColor: LunaSeaDatabaseValue.THEME_AMOLED.data ? Colors.black : LSColors.primary, + tooltipBgColor: LunaSeaDatabaseValue.THEME_AMOLED.data ? Colors.black : LunaColours.primary, tooltipRoundedRadius: Constants.UI_BORDER_RADIUS, tooltipPadding: EdgeInsets.all(8.0), maxContentWidth: MediaQuery.of(context).size.width/1.25, @@ -66,7 +66,7 @@ class TautulliLineGraphHelper { (index) => LineTooltipItem( [ '${data.series[spots[index].barIndex].name}: ', - Provider.of(context, listen: false).graphYAxis == TautulliGraphYAxis.PLAYS + context.read().graphYAxis == TautulliGraphYAxis.PLAYS ? '${spots[index]?.y?.truncate() ?? 0}' : '${Duration(seconds: spots[index]?.y?.truncate() ?? 0).lsDuration_fullTimestamp()}', ].join().trim(), diff --git a/lib/modules/tautulli/core/router.dart b/lib/modules/tautulli/core/router.dart index 0916b141..ccdcacd0 100644 --- a/lib/modules/tautulli/core/router.dart +++ b/lib/modules/tautulli/core/router.dart @@ -2,11 +2,9 @@ import 'package:fluro_fork/fluro_fork.dart'; import 'package:lunasea/modules/tautulli.dart'; class TautulliRouter { - static Router router = Router(); - TautulliRouter._(); - static void initialize() { + static void initialize(Router router) { TautulliHomeRouter.defineRoutes(router); // Details TautulliActivityDetailsRouter.defineRoutes(router); diff --git a/lib/modules/tautulli/core/state.dart b/lib/modules/tautulli/core/state.dart index ed5ebd8f..77e8286d 100644 --- a/lib/modules/tautulli/core/state.dart +++ b/lib/modules/tautulli/core/state.dart @@ -1,2 +1,806 @@ -export 'state/global.dart'; -export 'state/local.dart'; +import 'dart:async'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/tautulli.dart'; +import 'package:tautulli/tautulli.dart'; + +class TautulliState extends LunaGlobalState { + TautulliState() { + reset(); + } + + @override + void dispose() { + _getActivityTimer?.cancel(); + super.dispose(); + } + + @override + void reset() { + // Clear global data + _activity = null; + _users = null; + _history = null; + _syncedItems = null; + _search = null; + _statistics = null; + _recentlyAdded = null; + _loginLogs = null; + _newsletterLogs = null; + _notificationLogs = null; + _plexMediaScannerLogs = null; + _plexMediaServerLogs = null; + _tautulliLogs = null; + _dailyPlayCountGraph = null; + _playsByMonthGraph = null; + _playCountByDayOfWeekGraph = null; + _playCountByTopPlatformsGraph = null; + _playCountByTopUsersGraph = null; + _dailyStreamTypeBreakdownGraph = null; + _playCountBySourceResolutionGraph = null; + _playCountByStreamResolutionGraph = null; + _playCountByPlatformStreamTypeGraph = null; + _playCountByUserStreamTypeGraph = null; + _updatePlexMediaServer = null; + _updateTautulli = null; + _librariesTable = null; + _searchQuery = ''; + + // Clear user data + _userProfile = {}; + _userSyncedItems = {}; + _userIPs = {}; + _userWatchStats = {}; + _userPlayerStats = {}; + _userHistory = {}; + _metadata = {}; + _libraryWatchTimeStats = {}; + _libraryUserStats = {}; + _geolocationInformation = {}; + _whoisInformation = {}; + + // Reset global data + resetProfile(); + resetActivity(); + resetUsers(); + resetHistory(); + notifyListeners(); + } + + /////////////// + /// PROFILE /// + /////////////// + + /// API handler instance + Tautulli _api; + Tautulli get api => _api; + + /// Is the API enabled? + bool _enabled; + bool get enabled => _enabled; + + /// Tautulli host + String _host; + String get host => _host; + + /// Tautulli API key + String _apiKey; + String get apiKey => _apiKey; + + /// Headers to attach to all requests + Map _headers; + Map get headers => _headers; + + /// Reset the profile data, reinitializes API instance + void resetProfile() { + ProfileHiveObject _profile = Database.currentProfileObject; + // Copy profile into state + _enabled = _profile.tautulliEnabled ?? false; + _host = _profile.tautulliHost ?? ''; + _apiKey = _profile.tautulliKey ?? ''; + _headers = _profile.tautulliHeaders ?? {}; + // Create the API instance if Tautulli is enabled + _api = _enabled + ? Tautulli( + host: _host, + apiKey: _apiKey, + headers: Map.from(_headers), + ) + : null; + } + + //////////////// + /// ACTIVITY /// + //////////////// + + /// Timer to handle refreshing activity data + Timer _getActivityTimer; + + /// Create the periodic timer to handle refreshing activity data + void createActivityTimer() => _getActivityTimer = Timer.periodic( + Duration(seconds: TautulliDatabaseValue.REFRESH_RATE.data), + (_) => activity = _api.activity.getActivity(), + ); + + /// Cancel the periodic timer + void cancelActivityTimer() => _getActivityTimer?.cancel(); + + /// Storing activity data + Future _activity; + Future get activity => _activity; + set activity(Future activity) { + assert(activity != null); + _activity = activity; + notifyListeners(); + } + + /// Reset the activity by: + /// - Cancelling the timer + /// - Recreating the timer + /// - Setting the initial state of the future to an instance of the API call + void resetActivity() { + cancelActivityTimer(); + _activity = null; + if(_api != null) { + _activity = _api.activity.getActivity(); + createActivityTimer(); + } + notifyListeners(); + } + + ///////////// + /// USERS /// + ///////////// + + /// Storing the user table + Future _users; + Future get users => _users; + set users(Future users) { + assert(users != null); + _users = users; + notifyListeners(); + } + + /// Reset the users by: + /// - Setting the intial state of the future to an instance of the API call + /// - Resets individual user data maps + void resetUsers() { + // Reset user table + if(_api != null) { + _users = _api.users.getUsersTable( + length: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, + orderDirection: TautulliOrderDirection.ASCENDING, + orderColumn: TautulliUsersOrderColumn.FRIENDLY_NAME, + ); + } + notifyListeners(); + } + + /////////////// + /// HISTORY /// + /////////////// + + /// Storing the history table + Future _history; + Future get history => _history; + set history(Future history) { + assert(history != null); + _history = history; + notifyListeners(); + } + + /// Reset the history by: + /// - Setting the intial state of the future to an instance of the API call + void resetHistory() { + // Reset user table + if(_api != null) { + _history = _api.history.getHistory( + length: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, + orderDirection: TautulliOrderDirection.ASCENDING, + ); + } + notifyListeners(); + } + + Map> _individualHistory = {}; + Map> get individualHistory => _individualHistory; + void setIndividualHistory(int userId, Future data) { + assert(userId != null); + assert(data != null); + _individualHistory[userId] = data; + notifyListeners(); + } + + ////////////////// + /// STATISTICS /// + ////////////////// + + /// Stores the time range for the statistics + TautulliStatisticsTimeRange _statisticsTimeRange = TautulliStatisticsTimeRange.ONE_MONTH; + TautulliStatisticsTimeRange get statisticsTimeRange => _statisticsTimeRange; + set statisticsTimeRange(TautulliStatisticsTimeRange statisticsTimeRange) { + assert(statisticsTimeRange != null); + _statisticsTimeRange = statisticsTimeRange; + notifyListeners(); + } + + /// Stores the type of statistics + TautulliStatsType _statisticsType = TautulliStatsType.PLAYS; + TautulliStatsType get statisticsType => _statisticsType; + set statisticsType(TautulliStatsType statisticsType) { + assert(statisticsType != null); + _statisticsType = statisticsType; + notifyListeners(); + } + + ///////////////// + /// USER DATA /// + ///////////////// + + Map> _userProfile = {}; + Map> get userProfile => _userProfile; + void setUserProfile(int userId, Future data) { + assert(userId != null); + assert(data != null); + _userProfile[userId] = data; + notifyListeners(); + } + + Map>> _userSyncedItems = {}; + Map>> get userSyncedItems => _userSyncedItems; + void setUserSyncedItems(int userId, Future> data) { + assert(userId != null); + assert(data != null); + _userSyncedItems[userId] = data; + notifyListeners(); + } + + Map> _userIPs = {}; + Map> get userIPs => _userIPs; + void setUserIPs(int userId, Future data) { + assert(userId != null); + assert(data != null); + _userIPs[userId] = data; + notifyListeners(); + } + + Map>> _userWatchStats = {}; + Map>> get userWatchStats => _userWatchStats; + void setUserWatchStats(int userId, Future> data) { + assert(userId != null); + assert(data != null); + _userWatchStats[userId] = data; + notifyListeners(); + } + + Map>> _userPlayerStats = {}; + Map>> get userPlayerStats => _userPlayerStats; + void setUserPlayerStats(int userId, Future> data) { + assert(userId != null); + assert(data != null); + _userPlayerStats[userId] = data; + notifyListeners(); + } + + Map> _userHistory = {}; + Map> get userHistory => _userHistory; + void setUserHistory(int userId, Future data) { + assert(userId != null); + assert(data != null); + _userHistory[userId] = data; + notifyListeners(); + } + + //////////////////// + /// SYNCED ITEMS /// + //////////////////// + + Future> _syncedItems; + Future> get syncedItems => _syncedItems; + set syncedItems(Future> syncedItems) { + assert(syncedItems != null); + _syncedItems = syncedItems; + notifyListeners(); + } + + void resetSyncedItems() { + if(_api != null) _syncedItems = _api.libraries.getSyncedItems(); + notifyListeners(); + } + + ////////////////// + /// STATISTICS /// + ////////////////// + + Future> _statistics; + Future> get statistics => _statistics; + set statistics(Future> statistics) { + assert(statistics != null); + _statistics = statistics; + notifyListeners(); + } + + void resetStatistics() { + if(_api != null) _statistics = _api.history.getHomeStats( + timeRange: _statisticsTimeRange?.value, + statsType: _statisticsType, + statsCount: TautulliDatabaseValue.STATISTICS_STATS_COUNT.data, + ); + notifyListeners(); + } + + ////////////////////// + /// RECENTLY ADDED /// + ////////////////////// + + Future> _recentlyAdded; + Future> get recentlyAdded => _recentlyAdded; + set recentlyAdded(Future> recentlyAdded) { + assert(recentlyAdded != null); + _recentlyAdded = recentlyAdded; + notifyListeners(); + } + + void resetRecentlyAdded() { + if(_api != null) _recentlyAdded = _api.libraries.getRecentlyAdded( + count: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, + ); + notifyListeners(); + } + + //////////// + /// LOGS /// + //////////// + + Future _loginLogs; + Future get loginLogs => _loginLogs; + set loginLogs(Future loginLogs) { + assert(loginLogs != null); + _loginLogs = loginLogs; + notifyListeners(); + } + + void resetLoginLogs() { + if(_api != null) _loginLogs = _api.users.getUserLogins( + length: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, + ); + notifyListeners(); + } + + Future _newsletterLogs; + Future get newsletterLogs => _newsletterLogs; + set newsletterLogs(Future newsletterLogs) { + assert(newsletterLogs != null); + _newsletterLogs = newsletterLogs; + notifyListeners(); + } + + void resetNewsletterLogs() { + if(_api != null) _newsletterLogs = _api.notifications.getNewsletterLog( + length: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, + ); + notifyListeners(); + } + + Future _notificationLogs; + Future get notificationLogs => _notificationLogs; + set notificationLogs(Future notificationLogs) { + assert(notificationLogs != null); + _notificationLogs = notificationLogs; + notifyListeners(); + } + + void resetNotificationLogs() { + if(_api != null) _notificationLogs = _api.notifications.getNotificationLog( + length: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, + ); + notifyListeners(); + } + + Future> _plexMediaScannerLogs; + Future> get plexMediaScannerLogs => _plexMediaScannerLogs; + set plexMediaScannerLogs(Future> plexMediaScannerLogs) { + assert(plexMediaScannerLogs != null); + _plexMediaScannerLogs = plexMediaScannerLogs; + notifyListeners(); + } + + void resetPlexMediaScannerLogs() { + if(_api != null) _plexMediaScannerLogs = _api.miscellaneous.getPlexLog( + window: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, + logType: TautulliPlexLogType.SCANNER, + ); + notifyListeners(); + } + + Future> _plexMediaServerLogs; + Future> get plexMediaServerLogs => _plexMediaServerLogs; + set plexMediaServerLogs(Future> plexMediaServerLogs) { + assert(plexMediaServerLogs != null); + _plexMediaServerLogs = plexMediaServerLogs; + notifyListeners(); + } + + void resetPlexMediaServerLogs() { + if(_api != null) _plexMediaServerLogs = _api.miscellaneous.getPlexLog( + window: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, + logType: TautulliPlexLogType.SERVER, + ); + notifyListeners(); + } + + Future> _tautulliLogs; + Future> get tautulliLogs => _tautulliLogs; + set tautulliLogs(Future> tautulliLogs) { + assert(tautulliLogs != null); + _tautulliLogs = tautulliLogs; + notifyListeners(); + } + + void resetTautulliLogs() { + if(_api != null) _tautulliLogs = _api.miscellaneous.getLogs( + start: 0, + end: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, + ); + notifyListeners(); + } + + ////////////// + /// GRAPHS /// + ////////////// + + /// Store the graph Y axis + TautulliGraphYAxis _graphYAxis = TautulliGraphYAxis.PLAYS; + TautulliGraphYAxis get graphYAxis => _graphYAxis; + set graphYAxis(TautulliGraphYAxis graphYAxis) { + assert(graphYAxis != null); + _graphYAxis = graphYAxis; + notifyListeners(); + } + + Future _dailyPlayCountGraph; + Future get dailyPlayCountGraph => _dailyPlayCountGraph; + set dailyPlayCountGraph(Future dailyPlayCountGraph) { + assert(dailyPlayCountGraph != null); + _dailyPlayCountGraph = dailyPlayCountGraph; + notifyListeners(); + } + + void resetDailyPlayCountGraph() { + if(_api != null) _dailyPlayCountGraph = _api.history.getPlaysByDate( + timeRange: TautulliDatabaseValue.GRAPHS_LINECHART_DAYS.data, + yAxis: _graphYAxis, + ); + notifyListeners(); + } + + Future _playsByMonthGraph; + Future get playsByMonthGraph => _playsByMonthGraph; + set playsByMonthGraph(Future playsByMonthGraph) { + assert(playsByMonthGraph != null); + _playsByMonthGraph = playsByMonthGraph; + notifyListeners(); + } + + void resetPlaysByMonthGraph() { + if(_api != null) _playsByMonthGraph = _api.history.getPlaysPerMonth( + timeRange: TautulliDatabaseValue.GRAPHS_MONTHS.data, + yAxis: _graphYAxis, + ); + notifyListeners(); + } + + Future _playCountByDayOfWeekGraph; + Future get playCountByDayOfWeekGraph => _playCountByDayOfWeekGraph; + set playCountByDayOfWeekGraph(Future playCountByDayOfWeekGraph) { + assert(playCountByDayOfWeekGraph != null); + _playCountByDayOfWeekGraph = playCountByDayOfWeekGraph; + notifyListeners(); + } + + void resetPlayCountByDayOfWeekGraph() { + if(_api != null) _playCountByDayOfWeekGraph = _api.history.getPlaysByDayOfWeek( + timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data, + yAxis: _graphYAxis, + ); + notifyListeners(); + } + + Future _playCountByTopPlatformsGraph; + Future get playCountByTopPlatformsGraph => _playCountByTopPlatformsGraph; + set playCountByTopPlatformsGraph(Future playCountByTopPlatformsGraph) { + assert(playCountByTopPlatformsGraph != null); + _playCountByTopPlatformsGraph = playCountByTopPlatformsGraph; + notifyListeners(); + } + + void resetPlayCountByTopPlatformsGraph() { + if(_api != null) _playCountByTopPlatformsGraph = _api.history.getPlaysByTopTenPlatforms( + timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data, + yAxis: _graphYAxis, + ); + notifyListeners(); + } + + Future _playCountByTopUsersGraph; + Future get playCountByTopUsersGraph => _playCountByTopUsersGraph; + set playCountByTopUsersGraph(Future playCountByTopUsersGraph) { + assert(playCountByTopUsersGraph != null); + _playCountByTopUsersGraph = playCountByTopUsersGraph; + notifyListeners(); + } + + void resetPlayCountByTopUsersGraph() { + if(_api != null) _playCountByTopUsersGraph = _api.history.getPlaysByTopTenUsers( + timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data, + yAxis: _graphYAxis, + ); + notifyListeners(); + } + + void resetAllPlayPeriodGraphs() { + resetDailyPlayCountGraph(); + resetPlaysByMonthGraph(); + resetPlayCountByDayOfWeekGraph(); + resetPlayCountByTopPlatformsGraph(); + resetPlayCountByTopUsersGraph(); + } + + Future _dailyStreamTypeBreakdownGraph; + Future get dailyStreamTypeBreakdownGraph => _dailyStreamTypeBreakdownGraph; + set dailyStreamTypeBreakdownGraph(Future dailyStreamTypeBreakdownGraph) { + assert(dailyStreamTypeBreakdownGraph != null); + _dailyStreamTypeBreakdownGraph = dailyStreamTypeBreakdownGraph; + notifyListeners(); + } + + void resetDailyStreamTypeBreakdownGraph() { + if(_api != null) _dailyStreamTypeBreakdownGraph = _api.history.getPlaysByStreamType( + timeRange: TautulliDatabaseValue.GRAPHS_LINECHART_DAYS.data, + yAxis: _graphYAxis, + ); + notifyListeners(); + } + + Future _playCountBySourceResolutionGraph; + Future get playCountBySourceResolutionGraph => _playCountBySourceResolutionGraph; + set playCountBySourceResolutionGraph(Future playCountBySourceResolutionGraph) { + assert(playCountBySourceResolutionGraph != null); + _playCountBySourceResolutionGraph = playCountBySourceResolutionGraph; + notifyListeners(); + } + + void resetPlayCountBySourceResolutionGraph() { + if(_api != null) _playCountBySourceResolutionGraph = _api.history.getPlaysBySourceResolution( + timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data, + yAxis: _graphYAxis, + ); + notifyListeners(); + } + + Future _playCountByStreamResolutionGraph; + Future get playCountByStreamResolutionGraph => _playCountByStreamResolutionGraph; + set playCountByStreamResolutionGraph(Future playCountByStreamResolutionGraph) { + assert(playCountByStreamResolutionGraph != null); + _playCountByStreamResolutionGraph = playCountByStreamResolutionGraph; + notifyListeners(); + } + + void resetPlayCountByStreamResolutionGraph() { + if(_api != null) _playCountByStreamResolutionGraph = _api.history.getPlaysByStreamResolution( + timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data, + yAxis: _graphYAxis, + ); + notifyListeners(); + } + + Future _playCountByPlatformStreamTypeGraph; + Future get playCountByPlatformStreamTypeGraph => _playCountByPlatformStreamTypeGraph; + set playCountByPlatformStreamTypeGraph(Future playCountByPlatformStreamTypeGraph) { + assert(playCountByPlatformStreamTypeGraph != null); + _playCountByPlatformStreamTypeGraph = playCountByPlatformStreamTypeGraph; + notifyListeners(); + } + + void resetPlayCountByPlatformStreamTypeGraph() { + if(_api != null) _playCountByPlatformStreamTypeGraph = _api.history.getStreamTypeByTopTenPlatforms( + timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data, + yAxis: _graphYAxis, + ); + notifyListeners(); + } + + Future _playCountByUserStreamTypeGraph; + Future get playCountByUserStreamTypeGraph => _playCountByUserStreamTypeGraph; + set playCountByUserStreamTypeGraph(Future playCountByUserStreamTypeGraph) { + assert(playCountByUserStreamTypeGraph != null); + _playCountByUserStreamTypeGraph = playCountByUserStreamTypeGraph; + notifyListeners(); + } + + void resetPlayCountByUserStreamTypeGraph() { + if(_api != null) _playCountByUserStreamTypeGraph = _api.history.getStreamTypeByTopTenUsers( + timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data, + yAxis: _graphYAxis, + ); + notifyListeners(); + } + + void resetAllStreamInformationGraphs() { + resetDailyStreamTypeBreakdownGraph(); + resetPlayCountBySourceResolutionGraph(); + resetPlayCountByStreamResolutionGraph(); + resetPlayCountByPlatformStreamTypeGraph(); + resetPlayCountByUserStreamTypeGraph(); + } + + /////////////// + /// UPDATES /// + /////////////// + + Future _updatePlexMediaServer; + Future get updatePlexMediaServer => _updatePlexMediaServer; + set updatePlexMediaServer(Future updatePlexMediaServer) { + assert(updatePlexMediaServer != null); + _updatePlexMediaServer = updatePlexMediaServer; + notifyListeners(); + } + + void resetUpdatePlexMediaServer() { + if(_api != null) _updatePlexMediaServer = _api.system.getPMSUpdate(); + notifyListeners(); + } + + Future _updateTautulli; + Future get updateTautulli => _updateTautulli; + set updateTautulli(Future updateTautulli) { + assert(updateTautulli != null); + _updateTautulli = updateTautulli; + notifyListeners(); + } + + void resetUpdateTautulli() { + if(_api != null) _updateTautulli = _api.system.updateCheck(); + notifyListeners(); + } + + void resetAllUpdates() { + resetUpdatePlexMediaServer(); + resetUpdateTautulli(); + } + + ///////////////// + /// LIBRARIES /// + ///////////////// + + Future _librariesTable; + Future get librariesTable => _librariesTable; + set librariesTable(Future librariesTable) { + assert(librariesTable != null); + _librariesTable = librariesTable; + notifyListeners(); + } + + void resetLibrariesTable() { + if(_api != null) _librariesTable = _api.libraries.getLibrariesTable( + length: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, + orderColumn: TautulliLibrariesOrderColumn.SECTION_NAME, + orderDirection: TautulliOrderDirection.ASCENDING, + ); + notifyListeners(); + } + + //////////////// + /// METADATA /// + //////////////// + + Map> _metadata = {}; + Map> get metadata => _metadata; + void setMetadata(int ratingKey, Future metadata) { + assert(ratingKey != null); + assert(metadata != null); + _metadata[ratingKey] = metadata; + notifyListeners(); + } + + ///////////////////// + /// LIBRARY STATS /// + ///////////////////// + + Map>> _libraryWatchTimeStats = {}; + Map>> get libraryWatchTimeStats => _libraryWatchTimeStats; + void fetchLibraryWatchTimeStats(int sectionId) { + assert(sectionId != null); + _libraryWatchTimeStats[sectionId] = _api.libraries.getLibraryWatchTimeStats(sectionId: sectionId); + notifyListeners(); + } + + Map>> _libraryUserStats = {}; + Map>> get libraryUserStats => _libraryUserStats; + void fetchLibraryUserStats(int sectionId) { + assert(sectionId != null); + _libraryUserStats[sectionId] = _api.libraries.getLibraryUserStats(sectionId: sectionId); + notifyListeners(); + } + + ////////////// + /// SEARCH /// + ////////////// + + String _searchQuery = ''; + String get searchQuery => _searchQuery; + set searchQuery(String searchQuery) { + assert(searchQuery != null); + _searchQuery = searchQuery; + notifyListeners(); + } + + Future _search; + Future get search => _search; + void fetchSearch() { + _search = _api.libraries.search( + query: _searchQuery, + limit: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, + ); + notifyListeners(); + } + + ////////////////// + /// IP ADDRESS /// + ////////////////// + + Map> _geolocationInformation = {}; + Map> get geolocationInformation => _geolocationInformation; + void fetchGeolocationInformation(String ipAddress) { + assert(ipAddress != null); + _geolocationInformation[ipAddress] = _api.miscellaneous.getGeoIPLookup(ipAddress: ipAddress); + notifyListeners(); + } + + Map> _whoisInformation = {}; + Map> get whoisInformation => _whoisInformation; + void fetchWHOISInformation(String ipAddress) { + assert(ipAddress != null); + _whoisInformation[ipAddress] = _api.miscellaneous.getWHOISLookup(ipAddress: ipAddress); + notifyListeners(); + } + + /********* + * IMAGES * + *********/ + + /// Get the direct URL to an image via `pms_image_proxy` using a rating key. + String getImageURLFromRatingKey(int ratingKey, { int width = 300 }) { + if(host.endsWith('/')) return [ + host, + 'api/v2?apikey=$apiKey', + '&cmd=pms_image_proxy', + '&rating_key=$ratingKey', + '&width=$width', + ].join(); + return [ + host, + '/api/v2?apikey=$apiKey', + '&cmd=pms_image_proxy', + '&rating_key=$ratingKey', + '&width=$width', + ].join(); + } + + /// Get the direct URL to an image via `pms_image_proxy` using an image path. + String getImageURLFromPath(String path, { int width = 300 }) { + if(host.endsWith('/')) return [ + host, + 'api/v2?apikey=$apiKey', + '&cmd=pms_image_proxy', + '&img=$path', + '&width=$width', + ].join(); + return [ + host, + '/api/v2?apikey=$apiKey', + '&cmd=pms_image_proxy', + '&img=$path', + '&width=$width', + ].join(); + } +} diff --git a/lib/modules/tautulli/core/state/global.dart b/lib/modules/tautulli/core/state/global.dart deleted file mode 100644 index 4d269fc1..00000000 --- a/lib/modules/tautulli/core/state/global.dart +++ /dev/null @@ -1,247 +0,0 @@ -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/tautulli.dart'; -import 'package:tautulli/tautulli.dart'; - -class TautulliState extends ChangeNotifier { - TautulliState() { - reset(initialize: true); - } - - /// Reset the state of Tautulli back to the default - /// - /// If `initialize` is true, resets everything, else it resets the profile + data. - /// If false, the navigation index, etc. are not reset. - void reset({ bool initialize = false }) { - if(initialize) { - _statisticsType = TautulliStatsType.PLAYS; - _statisticsTimeRange = TautulliStatisticsTimeRange.ONE_MONTH; - _graphYAxis = TautulliGraphYAxis.PLAYS; - } - resetProfile(); - resetActivity(); - resetUsers(); - resetHistory(); - notifyListeners(); - } - - GlobalKey rootNavigatorKey = GlobalKey(); - GlobalKey rootScaffoldKey = GlobalKey(); - - /********** - * PROFILE * - **********/ - - /// API handler instance - Tautulli _api; - Tautulli get api => _api; - - /// Is the API enabled? - bool _enabled; - bool get enabled => _enabled; - - /// Tautulli host - String _host; - String get host => _host; - - /// Tautulli API key - String _apiKey; - String get apiKey => _apiKey; - - /// Is strict TLS enabled? - bool _strictTLS; - bool get strictTLS => _strictTLS; - - /// Headers to attach to all requests - Map _headers; - Map get headers => _headers; - - /// Reset the profile data, reinitializes API instance - void resetProfile() { - ProfileHiveObject _profile = Database.currentProfileObject; - // Copy profile into state - _enabled = _profile.tautulliEnabled ?? false; - _host = _profile.tautulliHost ?? ''; - _apiKey = _profile.tautulliKey ?? ''; - _strictTLS = _profile.tautulliStrictTLS ?? true; - _headers = _profile.tautulliHeaders ?? {}; - // Create the API instance if Tautulli is enabled - _api = _enabled - ? Tautulli( - host: _host, - apiKey: _apiKey, - strictTLS: _strictTLS, - headers: Map.from(_headers), - ) - : null; - } - - /*********** - * ACTIVITY * - ************/ - - /// Timer to handle refreshing activity data - Timer _getActivityTimer; - - /// Create the periodic timer to handle refreshing activity data - void createActivityTimer() => _getActivityTimer = Timer.periodic( - Duration(seconds: TautulliDatabaseValue.REFRESH_RATE.data), - (_) => activity = _api.activity.getActivity(), - ); - - /// Cancel the periodic timer - void cancelActivityTimer() => _getActivityTimer?.cancel(); - - /// Storing activity data - Future _activity; - Future get activity => _activity; - set activity(Future activity) { - assert(activity != null); - _activity = activity; - notifyListeners(); - } - - /// Reset the activity by: - /// - Cancelling the timer - /// - Recreating the timer - /// - Setting the initial state of the future to an instance of the API call - void resetActivity() { - cancelActivityTimer(); - _activity = null; - if(_api != null) { - _activity = _api.activity.getActivity(); - createActivityTimer(); - } - notifyListeners(); - } - - /******** - * USERS * - ********/ - - /// Storing the user table - Future _users; - Future get users => _users; - set users(Future users) { - assert(users != null); - _users = users; - notifyListeners(); - } - - /// Reset the users by: - /// - Setting the intial state of the future to an instance of the API call - /// - Resets individual user data maps - void resetUsers() { - // Reset user table - if(_api != null) { - _users = _api.users.getUsersTable( - length: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, - orderDirection: TautulliOrderDirection.ASCENDING, - orderColumn: TautulliUsersOrderColumn.FRIENDLY_NAME, - ); - } - notifyListeners(); - } - - /********** - * HISTORY * - **********/ - - /// Storing the history table - Future _history; - Future get history => _history; - set history(Future history) { - assert(history != null); - _history = history; - notifyListeners(); - } - - /// Reset the history by: - /// - Setting the intial state of the future to an instance of the API call - void resetHistory() { - // Reset user table - if(_api != null) { - _history = _api.history.getHistory( - length: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, - orderDirection: TautulliOrderDirection.ASCENDING, - ); - } - notifyListeners(); - } - - /*********** - * STATISTICS - ***********/ - - /// Stores the time range for the statistics - TautulliStatisticsTimeRange _statisticsTimeRange; - TautulliStatisticsTimeRange get statisticsTimeRange => _statisticsTimeRange; - set statisticsTimeRange(TautulliStatisticsTimeRange statisticsTimeRange) { - assert(statisticsTimeRange != null); - _statisticsTimeRange = statisticsTimeRange; - notifyListeners(); - } - - /// Stores the type of statistics - TautulliStatsType _statisticsType; - TautulliStatsType get statisticsType => _statisticsType; - set statisticsType(TautulliStatsType statisticsType) { - assert(statisticsType != null); - _statisticsType = statisticsType; - notifyListeners(); - } - - /********* - * GRAPHS * - *********/ - - /// Store the graph Y axis - TautulliGraphYAxis _graphYAxis; - TautulliGraphYAxis get graphYAxis => _graphYAxis; - set graphYAxis(TautulliGraphYAxis graphYAxis) { - assert(graphYAxis != null); - _graphYAxis = graphYAxis; - notifyListeners(); - } - - /********* - * IMAGES * - *********/ - - /// Get the direct URL to an image via `pms_image_proxy` using a rating key. - String getImageURLFromRatingKey(int ratingKey, { int width = 300 }) { - if(host.endsWith('/')) return [ - host, - 'api/v2?apikey=$apiKey', - '&cmd=pms_image_proxy', - '&rating_key=$ratingKey', - '&width=$width', - ].join(); - return [ - host, - '/api/v2?apikey=$apiKey', - '&cmd=pms_image_proxy', - '&rating_key=$ratingKey', - '&width=$width', - ].join(); - } - - /// Get the direct URL to an image via `pms_image_proxy` using an image path. - String getImageURLFromPath(String path, { int width = 300 }) { - if(host.endsWith('/')) return [ - host, - 'api/v2?apikey=$apiKey', - '&cmd=pms_image_proxy', - '&img=$path', - '&width=$width', - ].join(); - return [ - host, - '/api/v2?apikey=$apiKey', - '&cmd=pms_image_proxy', - '&img=$path', - '&width=$width', - ].join(); - } -} diff --git a/lib/modules/tautulli/core/state/local.dart b/lib/modules/tautulli/core/state/local.dart deleted file mode 100644 index 5157bb7e..00000000 --- a/lib/modules/tautulli/core/state/local.dart +++ /dev/null @@ -1,571 +0,0 @@ -import 'dart:async'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/tautulli.dart'; -import 'package:tautulli/tautulli.dart'; - -class TautulliLocalState extends ChangeNotifier { - ///////////////// - /// USER DATA /// - ///////////////// - - Map> _userProfile = {}; - Map> get userProfile => _userProfile; - void setUserProfile(int userId, Future data) { - assert(userId != null); - assert(data != null); - _userProfile[userId] = data; - notifyListeners(); - } - - Map>> _userSyncedItems = {}; - Map>> get userSyncedItems => _userSyncedItems; - void setUserSyncedItems(int userId, Future> data) { - assert(userId != null); - assert(data != null); - _userSyncedItems[userId] = data; - notifyListeners(); - } - - Map> _userIPs = {}; - Map> get userIPs => _userIPs; - void setUserIPs(int userId, Future data) { - assert(userId != null); - assert(data != null); - _userIPs[userId] = data; - notifyListeners(); - } - - Map>> _userWatchStats = {}; - Map>> get userWatchStats => _userWatchStats; - void setUserWatchStats(int userId, Future> data) { - assert(userId != null); - assert(data != null); - _userWatchStats[userId] = data; - notifyListeners(); - } - - Map>> _userPlayerStats = {}; - Map>> get userPlayerStats => _userPlayerStats; - void setUserPlayerStats(int userId, Future> data) { - assert(userId != null); - assert(data != null); - _userPlayerStats[userId] = data; - notifyListeners(); - } - - Map> _userHistory = {}; - Map> get userHistory => _userHistory; - void setUserHistory(int userId, Future data) { - assert(userId != null); - assert(data != null); - _userHistory[userId] = data; - notifyListeners(); - } - - /////////////// - /// HISTORY /// - /////////////// - - Map> _history = {}; - Map> get history => _history; - void setHistory(int key, Future data) { - assert(key != null); - assert(data != null); - _history[key] = data; - notifyListeners(); - } - - //////////////////// - /// SYNCED ITEMS /// - //////////////////// - - Future> _syncedItems; - Future> get syncedItems => _syncedItems; - set syncedItems(Future> syncedItems) { - assert(syncedItems != null); - _syncedItems = syncedItems; - notifyListeners(); - } - - void resetSyncedItems(BuildContext context) { - TautulliState _state = Provider.of(context, listen: false); - if(_state.api != null) _syncedItems = _state.api.libraries.getSyncedItems(); - notifyListeners(); - } - - ////////////////// - /// STATISTICS /// - ////////////////// - - Future> _statistics; - Future> get statistics => _statistics; - set statistics(Future> statistics) { - assert(statistics != null); - _statistics = statistics; - notifyListeners(); - } - - void resetStatistics(BuildContext context) { - TautulliState _state = Provider.of(context, listen: false); - if(_state.api != null) _statistics = _state.api.history.getHomeStats( - timeRange: _state.statisticsTimeRange?.value, - statsType: _state.statisticsType, - statsCount: TautulliDatabaseValue.STATISTICS_STATS_COUNT.data, - ); - notifyListeners(); - } - - ////////////////////// - /// RECENTLY ADDED /// - ////////////////////// - - Future> _recentlyAdded; - Future> get recentlyAdded => _recentlyAdded; - set recentlyAdded(Future> recentlyAdded) { - assert(recentlyAdded != null); - _recentlyAdded = recentlyAdded; - notifyListeners(); - } - - void resetRecentlyAdded(BuildContext context) { - TautulliState _state = Provider.of(context, listen: false); - if(_state.api != null) _recentlyAdded = _state.api.libraries.getRecentlyAdded( - count: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, - ); - notifyListeners(); - } - - //////////// - /// LOGS /// - //////////// - - Future _loginLogs; - Future get loginLogs => _loginLogs; - set loginLogs(Future loginLogs) { - assert(loginLogs != null); - _loginLogs = loginLogs; - notifyListeners(); - } - - void resetLoginLogs(BuildContext context) { - TautulliState _state = Provider.of(context, listen: false); - if(_state.api != null) _loginLogs = _state.api.users.getUserLogins( - length: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, - ); - notifyListeners(); - } - - Future _newsletterLogs; - Future get newsletterLogs => _newsletterLogs; - set newsletterLogs(Future newsletterLogs) { - assert(newsletterLogs != null); - _newsletterLogs = newsletterLogs; - notifyListeners(); - } - - void resetNewsletterLogs(BuildContext context) { - TautulliState _state = Provider.of(context, listen: false); - if(_state.api != null) _newsletterLogs = _state.api.notifications.getNewsletterLog( - length: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, - ); - notifyListeners(); - } - - Future _notificationLogs; - Future get notificationLogs => _notificationLogs; - set notificationLogs(Future notificationLogs) { - assert(notificationLogs != null); - _notificationLogs = notificationLogs; - notifyListeners(); - } - - void resetNotificationLogs(BuildContext context) { - TautulliState _state = Provider.of(context, listen: false); - if(_state.api != null) _notificationLogs = _state.api.notifications.getNotificationLog( - length: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, - ); - notifyListeners(); - } - - Future> _plexMediaScannerLogs; - Future> get plexMediaScannerLogs => _plexMediaScannerLogs; - set plexMediaScannerLogs(Future> plexMediaScannerLogs) { - assert(plexMediaScannerLogs != null); - _plexMediaScannerLogs = plexMediaScannerLogs; - notifyListeners(); - } - - void resetPlexMediaScannerLogs(BuildContext context) { - TautulliState _state = Provider.of(context, listen: false); - if(_state.api != null) _plexMediaScannerLogs = _state.api.miscellaneous.getPlexLog( - window: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, - logType: TautulliPlexLogType.SCANNER, - ); - notifyListeners(); - } - - Future> _plexMediaServerLogs; - Future> get plexMediaServerLogs => _plexMediaServerLogs; - set plexMediaServerLogs(Future> plexMediaServerLogs) { - assert(plexMediaServerLogs != null); - _plexMediaServerLogs = plexMediaServerLogs; - notifyListeners(); - } - - void resetPlexMediaServerLogs(BuildContext context) { - TautulliState _state = Provider.of(context, listen: false); - if(_state.api != null) _plexMediaServerLogs = _state.api.miscellaneous.getPlexLog( - window: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, - logType: TautulliPlexLogType.SERVER, - ); - notifyListeners(); - } - - Future> _tautulliLogs; - Future> get tautulliLogs => _tautulliLogs; - set tautulliLogs(Future> tautulliLogs) { - assert(tautulliLogs != null); - _tautulliLogs = tautulliLogs; - notifyListeners(); - } - - void resetTautulliLogs(BuildContext context) { - TautulliState _state = Provider.of(context, listen: false); - if(_state.api != null) _tautulliLogs = _state.api.miscellaneous.getLogs( - start: 0, - end: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, - ); - notifyListeners(); - } - - ////////////// - /// GRAPHS /// - ////////////// - - Future _dailyPlayCountGraph; - Future get dailyPlayCountGraph => _dailyPlayCountGraph; - set dailyPlayCountGraph(Future dailyPlayCountGraph) { - assert(dailyPlayCountGraph != null); - _dailyPlayCountGraph = dailyPlayCountGraph; - notifyListeners(); - } - - void resetDailyPlayCountGraph(BuildContext context) { - TautulliState _state = Provider.of(context, listen: false); - if(_state.api != null) _dailyPlayCountGraph = _state.api.history.getPlaysByDate( - timeRange: TautulliDatabaseValue.GRAPHS_LINECHART_DAYS.data, - yAxis: _state.graphYAxis, - ); - notifyListeners(); - } - - Future _playsByMonthGraph; - Future get playsByMonthGraph => _playsByMonthGraph; - set playsByMonthGraph(Future playsByMonthGraph) { - assert(playsByMonthGraph != null); - _playsByMonthGraph = playsByMonthGraph; - notifyListeners(); - } - - void resetPlaysByMonthGraph(BuildContext context) { - TautulliState _state = Provider.of(context, listen: false); - if(_state.api != null) _playsByMonthGraph = _state.api.history.getPlaysPerMonth( - timeRange: TautulliDatabaseValue.GRAPHS_MONTHS.data, - yAxis: _state.graphYAxis, - ); - notifyListeners(); - } - - Future _playCountByDayOfWeekGraph; - Future get playCountByDayOfWeekGraph => _playCountByDayOfWeekGraph; - set playCountByDayOfWeekGraph(Future playCountByDayOfWeekGraph) { - assert(playCountByDayOfWeekGraph != null); - _playCountByDayOfWeekGraph = playCountByDayOfWeekGraph; - notifyListeners(); - } - - void resetPlayCountByDayOfWeekGraph(BuildContext context) { - TautulliState _state = Provider.of(context, listen: false); - if(_state.api != null) _playCountByDayOfWeekGraph = _state.api.history.getPlaysByDayOfWeek( - timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data, - yAxis: _state.graphYAxis, - ); - notifyListeners(); - } - - Future _playCountByTopPlatformsGraph; - Future get playCountByTopPlatformsGraph => _playCountByTopPlatformsGraph; - set playCountByTopPlatformsGraph(Future playCountByTopPlatformsGraph) { - assert(playCountByTopPlatformsGraph != null); - _playCountByTopPlatformsGraph = playCountByTopPlatformsGraph; - notifyListeners(); - } - - void resetPlayCountByTopPlatformsGraph(BuildContext context) { - TautulliState _state = Provider.of(context, listen: false); - if(_state.api != null) _playCountByTopPlatformsGraph = _state.api.history.getPlaysByTopTenPlatforms( - timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data, - yAxis: _state.graphYAxis, - ); - notifyListeners(); - } - - Future _playCountByTopUsersGraph; - Future get playCountByTopUsersGraph => _playCountByTopUsersGraph; - set playCountByTopUsersGraph(Future playCountByTopUsersGraph) { - assert(playCountByTopUsersGraph != null); - _playCountByTopUsersGraph = playCountByTopUsersGraph; - notifyListeners(); - } - - void resetPlayCountByTopUsersGraph(BuildContext context) { - TautulliState _state = Provider.of(context, listen: false); - if(_state.api != null) _playCountByTopUsersGraph = _state.api.history.getPlaysByTopTenUsers( - timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data, - yAxis: _state.graphYAxis, - ); - notifyListeners(); - } - - void resetAllPlayPeriodGraphs(BuildContext context) { - resetDailyPlayCountGraph(context); - resetPlaysByMonthGraph(context); - resetPlayCountByDayOfWeekGraph(context); - resetPlayCountByTopPlatformsGraph(context); - resetPlayCountByTopUsersGraph(context); - } - - Future _dailyStreamTypeBreakdownGraph; - Future get dailyStreamTypeBreakdownGraph => _dailyStreamTypeBreakdownGraph; - set dailyStreamTypeBreakdownGraph(Future dailyStreamTypeBreakdownGraph) { - assert(dailyStreamTypeBreakdownGraph != null); - _dailyStreamTypeBreakdownGraph = dailyStreamTypeBreakdownGraph; - notifyListeners(); - } - - void resetDailyStreamTypeBreakdownGraph(BuildContext context) { - TautulliState _state = Provider.of(context, listen: false); - if(_state.api != null) _dailyStreamTypeBreakdownGraph = _state.api.history.getPlaysByStreamType( - timeRange: TautulliDatabaseValue.GRAPHS_LINECHART_DAYS.data, - yAxis: _state.graphYAxis, - ); - notifyListeners(); - } - - Future _playCountBySourceResolutionGraph; - Future get playCountBySourceResolutionGraph => _playCountBySourceResolutionGraph; - set playCountBySourceResolutionGraph(Future playCountBySourceResolutionGraph) { - assert(playCountBySourceResolutionGraph != null); - _playCountBySourceResolutionGraph = playCountBySourceResolutionGraph; - notifyListeners(); - } - - void resetPlayCountBySourceResolutionGraph(BuildContext context) { - TautulliState _state = Provider.of(context, listen: false); - if(_state.api != null) _playCountBySourceResolutionGraph = _state.api.history.getPlaysBySourceResolution( - timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data, - yAxis: _state.graphYAxis, - ); - notifyListeners(); - } - - Future _playCountByStreamResolutionGraph; - Future get playCountByStreamResolutionGraph => _playCountByStreamResolutionGraph; - set playCountByStreamResolutionGraph(Future playCountByStreamResolutionGraph) { - assert(playCountByStreamResolutionGraph != null); - _playCountByStreamResolutionGraph = playCountByStreamResolutionGraph; - notifyListeners(); - } - - void resetPlayCountByStreamResolutionGraph(BuildContext context) { - TautulliState _state = Provider.of(context, listen: false); - if(_state.api != null) _playCountByStreamResolutionGraph = _state.api.history.getPlaysByStreamResolution( - timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data, - yAxis: _state.graphYAxis, - ); - notifyListeners(); - } - - Future _playCountByPlatformStreamTypeGraph; - Future get playCountByPlatformStreamTypeGraph => _playCountByPlatformStreamTypeGraph; - set playCountByPlatformStreamTypeGraph(Future playCountByPlatformStreamTypeGraph) { - assert(playCountByPlatformStreamTypeGraph != null); - _playCountByPlatformStreamTypeGraph = playCountByPlatformStreamTypeGraph; - notifyListeners(); - } - - void resetPlayCountByPlatformStreamTypeGraph(BuildContext context) { - TautulliState _state = Provider.of(context, listen: false); - if(_state.api != null) _playCountByPlatformStreamTypeGraph = _state.api.history.getStreamTypeByTopTenPlatforms( - timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data, - yAxis: _state.graphYAxis, - ); - notifyListeners(); - } - - Future _playCountByUserStreamTypeGraph; - Future get playCountByUserStreamTypeGraph => _playCountByUserStreamTypeGraph; - set playCountByUserStreamTypeGraph(Future playCountByUserStreamTypeGraph) { - assert(playCountByUserStreamTypeGraph != null); - _playCountByUserStreamTypeGraph = playCountByUserStreamTypeGraph; - notifyListeners(); - } - - void resetPlayCountByUserStreamTypeGraph(BuildContext context) { - TautulliState _state = Provider.of(context, listen: false); - if(_state.api != null) _playCountByUserStreamTypeGraph = _state.api.history.getStreamTypeByTopTenUsers( - timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data, - yAxis: _state.graphYAxis, - ); - notifyListeners(); - } - - void resetAllStreamInformationGraphs(BuildContext context) { - resetDailyStreamTypeBreakdownGraph(context); - resetPlayCountBySourceResolutionGraph(context); - resetPlayCountByStreamResolutionGraph(context); - resetPlayCountByPlatformStreamTypeGraph(context); - resetPlayCountByUserStreamTypeGraph(context); - } - - /////////////// - /// UPDATES /// - /////////////// - - Future _updatePlexMediaServer; - Future get updatePlexMediaServer => _updatePlexMediaServer; - set updatePlexMediaServer(Future updatePlexMediaServer) { - assert(updatePlexMediaServer != null); - _updatePlexMediaServer = updatePlexMediaServer; - notifyListeners(); - } - - void resetUpdatePlexMediaServer(BuildContext context) { - TautulliState _state = Provider.of(context, listen: false); - if(_state.api != null) _updatePlexMediaServer = _state.api.system.getPMSUpdate(); - notifyListeners(); - } - - Future _updateTautulli; - Future get updateTautulli => _updateTautulli; - set updateTautulli(Future updateTautulli) { - assert(updateTautulli != null); - _updateTautulli = updateTautulli; - notifyListeners(); - } - - void resetUpdateTautulli(BuildContext context) { - TautulliState _state = Provider.of(context, listen: false); - if(_state.api != null) _updateTautulli = _state.api.system.updateCheck(); - notifyListeners(); - } - - void resetAllUpdates(BuildContext context) { - resetUpdatePlexMediaServer(context); - resetUpdateTautulli(context); - } - - ///////////////// - /// LIBRARIES /// - ///////////////// - - Future _librariesTable; - Future get librariesTable => _librariesTable; - set librariesTable(Future librariesTable) { - assert(librariesTable != null); - _librariesTable = librariesTable; - notifyListeners(); - } - - void resetLibrariesTable(BuildContext context) { - TautulliState _state = Provider.of(context, listen: false); - if(_state.api != null) _librariesTable = _state.api.libraries.getLibrariesTable( - length: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, - orderColumn: TautulliLibrariesOrderColumn.SECTION_NAME, - orderDirection: TautulliOrderDirection.ASCENDING, - ); - notifyListeners(); - } - - //////////////// - /// METADATA /// - //////////////// - - Map> _metadata = {}; - Map> get metadata => _metadata; - void setMetadata(int ratingKey, Future metadata) { - assert(ratingKey != null); - assert(metadata != null); - _metadata[ratingKey] = metadata; - notifyListeners(); - } - - ///////////////////// - /// LIBRARY STATS /// - ///////////////////// - - Map>> _libraryWatchTimeStats = {}; - Map>> get libraryWatchTimeStats => _libraryWatchTimeStats; - void fetchLibraryWatchTimeStats(BuildContext context, int sectionId) { - assert(sectionId != null); - TautulliState _state = Provider.of(context, listen: false); - _libraryWatchTimeStats[sectionId] = _state.api.libraries.getLibraryWatchTimeStats(sectionId: sectionId); - notifyListeners(); - } - - Map>> _libraryUserStats = {}; - Map>> get libraryUserStats => _libraryUserStats; - void fetchLibraryUserStats(BuildContext context, int sectionId) { - assert(sectionId != null); - TautulliState _state = Provider.of(context, listen: false); - _libraryUserStats[sectionId] = _state.api.libraries.getLibraryUserStats(sectionId: sectionId); - notifyListeners(); - } - - ////////////// - /// SEARCH /// - ////////////// - - String _searchQuery = ''; - String get searchQuery => _searchQuery; - set searchQuery(String searchQuery) { - assert(searchQuery != null); - _searchQuery = searchQuery; - notifyListeners(); - } - - Future _search; - Future get search => _search; - void fetchSearch(BuildContext context) { - TautulliState _state = Provider.of(context, listen: false); - _search = _state.api.libraries.search( - query: _searchQuery, - limit: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, - ); - notifyListeners(); - } - - ////////////////// - /// IP ADDRESS /// - ////////////////// - - Map> _geolocationInformation = {}; - Map> get geolocationInformation => _geolocationInformation; - void fetchGeolocationInformation(BuildContext context, String ipAddress) { - assert(ipAddress != null); - TautulliState _state = Provider.of(context, listen: false); - _geolocationInformation[ipAddress] = _state.api.miscellaneous.getGeoIPLookup(ipAddress: ipAddress); - notifyListeners(); - } - - Map> _whoisInformation = {}; - Map> get whoisInformation => _whoisInformation; - void fetchWHOISInformation(BuildContext context, String ipAddress) { - assert(ipAddress != null); - TautulliState _state = Provider.of(context, listen: false); - _whoisInformation[ipAddress] = _state.api.miscellaneous.getWHOISLookup(ipAddress: ipAddress); - notifyListeners(); - } -} diff --git a/lib/modules/tautulli/main.dart b/lib/modules/tautulli/main.dart deleted file mode 100644 index f1711a84..00000000 --- a/lib/modules/tautulli/main.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/tautulli.dart'; - -class TautulliModule extends StatefulWidget { - static const String ROUTE_NAME = '/tautulli'; - final String profile; - - TautulliModule({ - Key key, - this.profile, - }) : super(key: key); - - @override - State createState() => _State(); -} - -class _State extends State { - @override - Widget build(BuildContext context) => ChangeNotifierProvider( - create: (_) => TautulliLocalState(), - child: WillPopScope( - onWillPop: _onWillPop, - child: Navigator( - key: Provider.of(context, listen: false).rootNavigatorKey, - initialRoute: TautulliHomeRouter.route(profile: widget.profile), - onGenerateRoute: TautulliRouter.router.generator, - ), - ), - ); - - Future _onWillPop() async { - TautulliState _state = Provider.of(context, listen: false); - if(_state.rootNavigatorKey.currentState.canPop()) { - _state.rootNavigatorKey.currentState.pop(); - } else if(_state.rootScaffoldKey.currentState.hasDrawer) { - _state.rootScaffoldKey.currentState.isDrawerOpen - ? _state.rootNavigatorKey.currentState.pop() - : _state.rootScaffoldKey.currentState.openDrawer(); - } - return false; - } -} diff --git a/lib/modules/tautulli/modules/activity/route.dart b/lib/modules/tautulli/modules/activity/route.dart index 55f3142f..5e5334d9 100644 --- a/lib/modules/tautulli/modules/activity/route.dart +++ b/lib/modules/tautulli/modules/activity/route.dart @@ -29,9 +29,8 @@ class _State extends State with AutomaticKeepAliveClientM } Future _refresh() async { - TautulliState _state = Provider.of(context, listen: false); - _state.resetActivity(); - await _state.activity; + context.read().resetActivity(); + await context.read().activity; } Widget get _body => LSRefreshIndicator( @@ -44,7 +43,7 @@ class _State extends State with AutomaticKeepAliveClientM builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( 'TautulliActivityRoute', '_body', 'Unable to fetch Tautulli activity', diff --git a/lib/modules/tautulli/modules/activity/widgets/activity_tile.dart b/lib/modules/tautulli/modules/activity/widgets/activity_tile.dart index f9455b50..222edbd3 100644 --- a/lib/modules/tautulli/modules/activity/widgets/activity_tile.dart +++ b/lib/modules/tautulli/modules/activity/widgets/activity_tile.dart @@ -28,12 +28,11 @@ class TautulliActivityTile extends StatelessWidget { ), decoration: session.art != null && session.art.isNotEmpty ? LSCardBackground( - uri: Provider.of(context, listen: false).getImageURLFromPath( + uri: context.watch().getImageURLFromPath( session.art, width: MediaQuery.of(context).size.width.truncate(), ), - headers: Provider.of(context, listen: false).headers, - darken: true, + headers: context.watch().headers, ) : null, ); @@ -95,7 +94,7 @@ class TautulliActivityTile extends StatelessWidget { placeholder: 'assets/images/sonarr/noseriesposter.png', height: _height, width: _width, - headers: Provider.of(context, listen: false).headers.cast(), + headers: context.watch().headers.cast(), ); Widget get _user => Row( @@ -115,15 +114,15 @@ class TautulliActivityTile extends StatelessWidget { LinearPercentIndicator( percent: session.lsTranscodeProgress, padding: EdgeInsets.symmetric(horizontal: 2.0), - progressColor: LSColors.splash.withOpacity(0.30), + progressColor: LunaColours.splash.withOpacity(0.30), backgroundColor: Colors.transparent, lineHeight: 4.0, ), LinearPercentIndicator( percent: session.lsProgressPercent, padding: EdgeInsets.symmetric(horizontal: 2.0), - progressColor: LSColors.accent, - backgroundColor: LSColors.accent.withOpacity(0.15), + progressColor: LunaColours.accent, + backgroundColor: LunaColours.accent.withOpacity(0.15), lineHeight: 4.0, ), ], diff --git a/lib/modules/tautulli/modules/activity_details/route.dart b/lib/modules/tautulli/modules/activity_details/route.dart index abd49d84..d88226bd 100644 --- a/lib/modules/tautulli/modules/activity_details/route.dart +++ b/lib/modules/tautulli/modules/activity_details/route.dart @@ -9,41 +9,20 @@ class TautulliActivityDetailsRouter { static Future navigateTo(BuildContext context, { @required String sessionId, - }) async => TautulliRouter.router.navigateTo( + }) async => LunaRouter.router.navigateTo( context, route(sessionId: sessionId), ); - static String route({ - String profile, - @required String sessionId, - }) => [ - ROUTE_NAME.replaceFirst(':sessionid', sessionId ?? '0'), - if(profile != null) '/$profile', - ].join(); + static String route({ @required String sessionId }) => ROUTE_NAME.replaceFirst(':sessionid', sessionId ?? '0'); static void defineRoutes(Router router) { - /// With profile defined - router.define( - ROUTE_NAME + '/:profile', - handler: Handler(handlerFunc: (context, params) => _TautulliActivityDetailsRoute( - sessionId: params['sessionid'] != null && params['sessionid'].length != 0 - ? params['sessionid'][0] ?? '-1' - : '-1', - profile: params['profile'] != null && params['profile'].length != 0 - ? params['profile'][0] - : null, - )), - transitionType: LunaRouter.transitionType, - ); - /// Without profile defined router.define( ROUTE_NAME, handler: Handler(handlerFunc: (context, params) => _TautulliActivityDetailsRoute( sessionId: params['sessionid'] != null && params['sessionid'].length != 0 ? params['sessionid'][0] ?? '-1' : '-1', - profile: null, )), transitionType: LunaRouter.transitionType, ); @@ -53,12 +32,10 @@ class TautulliActivityDetailsRouter { } class _TautulliActivityDetailsRoute extends StatefulWidget { - final String profile; final String sessionId; _TautulliActivityDetailsRoute({ Key key, - @required this.profile, @required this.sessionId, }): super(key: key); @@ -71,9 +48,8 @@ class _State extends State<_TautulliActivityDetailsRoute> { final GlobalKey _refreshKey = GlobalKey(); Future _refresh() async { - TautulliState _state = Provider.of(context, listen: false); - _state.resetActivity(); - await _state.activity; + context.read().resetActivity(); + await context.read().activity; } @override @@ -83,8 +59,10 @@ class _State extends State<_TautulliActivityDetailsRoute> { body: _body, ); - Widget get _appBar => LSAppBar( + Widget get _appBar => LunaAppBar( + context: context, title: 'Activity Details', + popUntil: '/tautulli', actions: [ TautulliActivityDetailsUser(sessionId: widget.sessionId), TautulliActivityDetailsMetadata(sessionId: widget.sessionId), @@ -101,7 +79,7 @@ class _State extends State<_TautulliActivityDetailsRoute> { builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( '_TautulliActivityDetailsRoute', '_body', 'Unable to pull Tautulli activity session', @@ -130,6 +108,6 @@ class _State extends State<_TautulliActivityDetailsRoute> { text: 'Session Ended', showButton: true, buttonText: 'Back', - onTapHandler: () async => TautulliRouter.router.pop(context), + onTapHandler: () async => Navigator.of(context).pop(), ); } diff --git a/lib/modules/tautulli/modules/activity_details/widgets/information.dart b/lib/modules/tautulli/modules/activity_details/widgets/information.dart index a0d1bd97..6d1fd0ff 100644 --- a/lib/modules/tautulli/modules/activity_details/widgets/information.dart +++ b/lib/modules/tautulli/modules/activity_details/widgets/information.dart @@ -52,12 +52,10 @@ class TautulliActivityDetailsInformation extends StatelessWidget { LSTableContent(title: 'quality', body: session.lsQuality), LSTableContent(title: 'stream', body: session.lsStream), LSTableContent(title: 'container', body: session.lsContainer), - session.streamVideoDecision != null && session.streamVideoDecision != TautulliTranscodeDecision.NULL - ? LSTableContent(title: 'video', body: session.lsVideo) - : Container(), - session.streamAudioDecision != null && session.streamAudioDecision != TautulliTranscodeDecision.NULL - ? LSTableContent(title: 'audio', body: session.lsAudio) - : Container(), + if(session.streamVideoDecision != null && session.streamVideoDecision != TautulliTranscodeDecision.NULL) + LSTableContent(title: 'video', body: session.lsVideo), + if(session.streamAudioDecision != null && session.streamAudioDecision != TautulliTranscodeDecision.NULL) + LSTableContent(title: 'audio', body: session.lsAudio), ], ); } diff --git a/lib/modules/tautulli/modules/activity_details/widgets/terminate_session.dart b/lib/modules/tautulli/modules/activity_details/widgets/terminate_session.dart index 9eed2892..242db396 100644 --- a/lib/modules/tautulli/modules/activity_details/widgets/terminate_session.dart +++ b/lib/modules/tautulli/modules/activity_details/widgets/terminate_session.dart @@ -14,14 +14,14 @@ class TautulliActivityDetailsTerminateSession extends StatelessWidget { @override Widget build(BuildContext context) => LSButton( text: 'Terminate Session', - backgroundColor: LSColors.red, + backgroundColor: LunaColours.red, onTap: () async => _onPressed(context), ); Future _onPressed(BuildContext context) async { List _values = await TautulliDialogs.terminateSession(context); if(_values[0]) { - Provider.of(context, listen: false).api.activity.terminateSession( + context.read().api.activity.terminateSession( sessionId: session.sessionId, message: _values[1] != null && (_values[1] as String).isNotEmpty ? _values[1] : null, ) @@ -31,10 +31,10 @@ class TautulliActivityDetailsTerminateSession extends StatelessWidget { title: 'Terminated Session', message: '${session.friendlyName}\t${Constants.TEXT_EMDASH}\t${session.title}', ); - TautulliRouter.router.pop(context); + Navigator.of(context).pop(); }) .catchError((error, trace) { - Logger.error( + LunaLogger.error( 'TautulliActivityDetailsTerminateSession', '_onPressed', 'Unable to terminate session: ${session.sessionId}', diff --git a/lib/modules/tautulli/modules/check_for_updates/route.dart b/lib/modules/tautulli/modules/check_for_updates/route.dart index 260c0804..41b45b9c 100644 --- a/lib/modules/tautulli/modules/check_for_updates/route.dart +++ b/lib/modules/tautulli/modules/check_for_updates/route.dart @@ -8,31 +8,17 @@ import 'package:tautulli/tautulli.dart'; class TautulliCheckForUpdatesRouter { static const ROUTE_NAME = '/tautulli/more/checkforupdates'; - static Future navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( context, route(), ); - static String route({ String profile }) => [ - ROUTE_NAME, - if(profile != null) '/$profile', - ].join(); + static String route() => ROUTE_NAME; static void defineRoutes(Router router) { - router.define( - ROUTE_NAME + '/:profile', - handler: Handler(handlerFunc: (context, params) => _TautulliCheckForUpdatesRoute( - profile: params['profile'] != null && params['profile'].length != 0 - ? params['profile'][0] - : null, - )), - transitionType: LunaRouter.transitionType, - ); router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => _TautulliCheckForUpdatesRoute( - profile: null, - )), + handler: Handler(handlerFunc: (context, params) => _TautulliCheckForUpdatesRoute()), transitionType: LunaRouter.transitionType, ); } @@ -41,13 +27,6 @@ class TautulliCheckForUpdatesRouter { } class _TautulliCheckForUpdatesRoute extends StatefulWidget { - final String profile; - - _TautulliCheckForUpdatesRoute({ - Key key, - @required this.profile, - }) : super(key: key); - @override State createState() => _State(); } @@ -58,12 +37,11 @@ class _State extends State<_TautulliCheckForUpdatesRoute> { bool _initialLoad = false; Future _refresh() async { - TautulliLocalState _local = Provider.of(context, listen: false); - _local.resetAllUpdates(context); + context.read().resetAllUpdates(); setState(() => _initialLoad = true); await Future.wait([ - _local.updatePlexMediaServer, - _local.updateTautulli, + context.read().updatePlexMediaServer, + context.read().updateTautulli, ]); } @@ -80,20 +58,24 @@ class _State extends State<_TautulliCheckForUpdatesRoute> { body: _initialLoad ? _body : LSLoader(), ); - Widget get _appBar => LSAppBar(title: 'Check for Updates'); + Widget get _appBar => LunaAppBar( + context: context, + title: 'Check for Updates', + popUntil: '/tautulli', + ); Widget get _body => LSRefreshIndicator( refreshKey: _refreshKey, onRefresh: _refresh, child: FutureBuilder( future: Future.wait([ - Provider.of(context).updatePlexMediaServer, - Provider.of(context).updateTautulli, + context.watch().updatePlexMediaServer, + context.watch().updateTautulli, ]), builder: (context, AsyncSnapshot> snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( '_TautulliCheckForUpdatesRoute', '_body', 'Unable to fetch updates', diff --git a/lib/modules/tautulli/modules/check_for_updates/widgets/pms_tile.dart b/lib/modules/tautulli/modules/check_for_updates/widgets/pms_tile.dart index a15598f9..53524f24 100644 --- a/lib/modules/tautulli/modules/check_for_updates/widgets/pms_tile.dart +++ b/lib/modules/tautulli/modules/check_for_updates/widgets/pms_tile.dart @@ -22,7 +22,7 @@ class TautulliCheckForUpdatesPMSTile extends StatelessWidget { children: [ LSIconButton( icon: CustomIcons.plex, - color: LSColors.list(0), + color: LunaColours.list(0), ), ], crossAxisAlignment: CrossAxisAlignment.center, @@ -39,7 +39,7 @@ class TautulliCheckForUpdatesPMSTile extends StatelessWidget { if(!update.updateAvailable) TextSpan( text: 'No Updates Available\n', style: TextStyle( - color: LSColors.accent, + color: LunaColours.accent, fontWeight: FontWeight.w600, ), ), @@ -47,7 +47,7 @@ class TautulliCheckForUpdatesPMSTile extends StatelessWidget { if(update.updateAvailable) TextSpan( text: 'Update Available\n', style: TextStyle( - color: LSColors.orange, + color: LunaColours.orange, fontWeight: FontWeight.w600, ), ), diff --git a/lib/modules/tautulli/modules/check_for_updates/widgets/tautulli_tile.dart b/lib/modules/tautulli/modules/check_for_updates/widgets/tautulli_tile.dart index c4fadc95..ec10ec65 100644 --- a/lib/modules/tautulli/modules/check_for_updates/widgets/tautulli_tile.dart +++ b/lib/modules/tautulli/modules/check_for_updates/widgets/tautulli_tile.dart @@ -22,7 +22,7 @@ class TautulliCheckForUpdatesTautulliTile extends StatelessWidget { children: [ LSIconButton( icon: CustomIcons.tautulli, - color: LSColors.list(1), + color: LunaColours.list(1), ), ], crossAxisAlignment: CrossAxisAlignment.center, @@ -39,14 +39,14 @@ class TautulliCheckForUpdatesTautulliTile extends StatelessWidget { if(!update.update) TextSpan( text: 'No Updates Available\n', style: TextStyle( - color: LSColors.accent, + color: LunaColours.accent, fontWeight: FontWeight.w600, ), ), if(update.update) TextSpan( text: 'Update Available\n', style: TextStyle( - color: LSColors.orange, + color: LunaColours.orange, fontWeight: FontWeight.w600, ), ), diff --git a/lib/modules/tautulli/modules/graphs/route.dart b/lib/modules/tautulli/modules/graphs/route.dart index 57666764..6392e2d5 100644 --- a/lib/modules/tautulli/modules/graphs/route.dart +++ b/lib/modules/tautulli/modules/graphs/route.dart @@ -6,31 +6,17 @@ import 'package:lunasea/modules/tautulli.dart'; class TautulliGraphsRouter { static const String ROUTE_NAME = '/tautulli/more/graphs'; - static Future navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( context, route(), ); - static String route({ String profile }) => [ - ROUTE_NAME, - if(profile != null) '/$profile', - ].join(); + static String route() => ROUTE_NAME; static void defineRoutes(Router router) { - router.define( - ROUTE_NAME + '/:profile', - handler: Handler(handlerFunc: (context, params) => _TautulliGraphsRoute( - profile: params['profile'] != null && params['profile'].length != 0 - ? params['profile'][0] - : null, - )), - transitionType: LunaRouter.transitionType, - ); router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => _TautulliGraphsRoute( - profile: null, - )), + handler: Handler(handlerFunc: (context, params) => _TautulliGraphsRoute()), transitionType: LunaRouter.transitionType, ); } @@ -39,13 +25,6 @@ class TautulliGraphsRouter { } class _TautulliGraphsRoute extends StatefulWidget { - final String profile; - - _TautulliGraphsRoute({ - Key key, - @required this.profile, - }) : super(key: key); - @override State<_TautulliGraphsRoute> createState() => _State(); } @@ -68,8 +47,10 @@ class _State extends State<_TautulliGraphsRoute> { body: _body, ); - Widget get _appBar => LSAppBar( + Widget get _appBar => LunaAppBar( + context: context, title: 'Graphs', + popUntil: '/tautulli', actions: [ TautulliGraphsTypeButton(), ], diff --git a/lib/modules/tautulli/modules/graphs/widgets/type_button.dart b/lib/modules/tautulli/modules/graphs/widgets/type_button.dart index dc1ab582..f66e0edb 100644 --- a/lib/modules/tautulli/modules/graphs/widgets/type_button.dart +++ b/lib/modules/tautulli/modules/graphs/widgets/type_button.dart @@ -13,9 +13,9 @@ class TautulliGraphsTypeButton extends StatelessWidget { : LSRoundedShape(), icon: LSIcon(icon: Icons.merge_type), onSelected: (value) { - Provider.of(context, listen: false).graphYAxis = value; - Provider.of(context, listen: false).resetAllPlayPeriodGraphs(context); - Provider.of(context, listen: false).resetAllStreamInformationGraphs(context); + context.read().graphYAxis = value; + context.read().resetAllPlayPeriodGraphs(); + context.read().resetAllStreamInformationGraphs(); }, itemBuilder: (context) => List>.generate( TautulliStatsType.values.length, @@ -26,7 +26,7 @@ class TautulliGraphsTypeButton extends StatelessWidget { style: TextStyle( fontSize: Constants.UI_FONT_SIZE_SUBTITLE, color: type == TautulliGraphYAxis.values[index] - ? LSColors.accent + ? LunaColours.accent : Colors.white, ), ), diff --git a/lib/modules/tautulli/modules/graphs_play_by_period/route.dart b/lib/modules/tautulli/modules/graphs_play_by_period/route.dart index 75a5880b..61f39566 100644 --- a/lib/modules/tautulli/modules/graphs_play_by_period/route.dart +++ b/lib/modules/tautulli/modules/graphs_play_by_period/route.dart @@ -26,14 +26,13 @@ class _State extends State with AutomaticKeepAl } Future _refresh() async { - TautulliLocalState _state = Provider.of(context, listen: false); - _state.resetAllPlayPeriodGraphs(context); + context.read().resetAllPlayPeriodGraphs(); await Future.wait([ - _state.dailyPlayCountGraph, - _state.playsByMonthGraph, - _state.playCountByDayOfWeekGraph, - _state.playCountByTopPlatformsGraph, - _state.playCountByTopUsersGraph, + context.read().dailyPlayCountGraph, + context.read().playsByMonthGraph, + context.read().playCountByDayOfWeekGraph, + context.read().playCountByTopPlatformsGraph, + context.read().playCountByTopUsersGraph, ]); } diff --git a/lib/modules/tautulli/modules/graphs_play_by_period/widgets/daily_play_count_graph.dart b/lib/modules/tautulli/modules/graphs_play_by_period/widgets/daily_play_count_graph.dart index c0bdb34b..a2cceff0 100644 --- a/lib/modules/tautulli/modules/graphs_play_by_period/widgets/daily_play_count_graph.dart +++ b/lib/modules/tautulli/modules/graphs_play_by_period/widgets/daily_play_count_graph.dart @@ -7,14 +7,14 @@ import 'package:tautulli/tautulli.dart'; class TautulliGraphsDailyPlayCountGraph extends StatelessWidget { @override - Widget build(BuildContext context) => Selector>( + Widget build(BuildContext context) => Selector>( selector: (_, state) => state.dailyPlayCountGraph, builder: (context, future, _) => FutureBuilder( future: future, builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( 'TautulliGraphsDailyPlayCountGraph', '_body', 'Unable to fetch Tautulli graph data: getPlaysByDate', diff --git a/lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_day_of_week_graph.dart b/lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_day_of_week_graph.dart index 826115a0..65bd4ba4 100644 --- a/lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_day_of_week_graph.dart +++ b/lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_day_of_week_graph.dart @@ -6,14 +6,14 @@ import 'package:tautulli/tautulli.dart'; class TautulliGraphsPlayCountByDayOfWeekGraph extends StatelessWidget { @override - Widget build(BuildContext context) => Selector>( + Widget build(BuildContext context) => Selector>( selector: (_, state) => state.playCountByDayOfWeekGraph, builder: (context, future, _) => FutureBuilder( future: future, builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( 'TautulliGraphsPlayCountByDayOfWeekGraph', '_body', 'Unable to fetch Tautulli graph data: getPlaysByDayOfWeek', diff --git a/lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_top_platforms_graph.dart b/lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_top_platforms_graph.dart index f1978513..debdc998 100644 --- a/lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_top_platforms_graph.dart +++ b/lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_top_platforms_graph.dart @@ -6,14 +6,14 @@ import 'package:tautulli/tautulli.dart'; class TautulliGraphsPlayCountByTopPlatformsGraph extends StatelessWidget { @override - Widget build(BuildContext context) => Selector>( + Widget build(BuildContext context) => Selector>( selector: (_, state) => state.playCountByTopPlatformsGraph, builder: (context, future, _) => FutureBuilder( future: future, builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( 'TautulliGraphsPlayCountByTopPlatformsGraph', '_body', 'Unable to fetch Tautulli graph data: getPlaysByTopTenPlatforms', diff --git a/lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_top_users_graph.dart b/lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_top_users_graph.dart index fa760b56..873935c3 100644 --- a/lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_top_users_graph.dart +++ b/lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_top_users_graph.dart @@ -6,14 +6,14 @@ import 'package:tautulli/tautulli.dart'; class TautulliGraphsPlayCountByTopUsersGraph extends StatelessWidget { @override - Widget build(BuildContext context) => Selector>( + Widget build(BuildContext context) => Selector>( selector: (_, state) => state.playCountByTopUsersGraph, builder: (context, future, _) => FutureBuilder( future: future, builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( 'TautulliGraphsPlayCountByTopUsersGraph', '_body', 'Unable to fetch Tautulli graph data: getPlaysByTopTenUsers', diff --git a/lib/modules/tautulli/modules/graphs_play_by_period/widgets/plays_by_month_graph.dart b/lib/modules/tautulli/modules/graphs_play_by_period/widgets/plays_by_month_graph.dart index c409b8f0..5bb0e835 100644 --- a/lib/modules/tautulli/modules/graphs_play_by_period/widgets/plays_by_month_graph.dart +++ b/lib/modules/tautulli/modules/graphs_play_by_period/widgets/plays_by_month_graph.dart @@ -6,14 +6,14 @@ import 'package:tautulli/tautulli.dart'; class TautulliGraphsPlaysByMonthGraph extends StatelessWidget { @override - Widget build(BuildContext context) => Selector>( + Widget build(BuildContext context) => Selector>( selector: (_, state) => state.playsByMonthGraph, builder: (context, future, _) => FutureBuilder( future: future, builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( 'TautulliGraphsPlaysByMonthGraph', '_body', 'Unable to fetch Tautulli graph data: getPlaysByMonth', diff --git a/lib/modules/tautulli/modules/graphs_stream_information/route.dart b/lib/modules/tautulli/modules/graphs_stream_information/route.dart index 2eac7d43..2910cd98 100644 --- a/lib/modules/tautulli/modules/graphs_stream_information/route.dart +++ b/lib/modules/tautulli/modules/graphs_stream_information/route.dart @@ -26,14 +26,13 @@ class _State extends State with AutomaticK } Future _refresh() async { - TautulliLocalState _state = Provider.of(context, listen: false); - _state.resetAllStreamInformationGraphs(context); + context.read().resetAllStreamInformationGraphs(); await Future.wait([ - _state.dailyStreamTypeBreakdownGraph, - _state.playCountBySourceResolutionGraph, - _state.playCountByStreamResolutionGraph, - _state.playCountByPlatformStreamTypeGraph, - _state.playCountByUserStreamTypeGraph, + context.read().dailyStreamTypeBreakdownGraph, + context.read().playCountBySourceResolutionGraph, + context.read().playCountByStreamResolutionGraph, + context.read().playCountByPlatformStreamTypeGraph, + context.read().playCountByUserStreamTypeGraph, ]); } diff --git a/lib/modules/tautulli/modules/graphs_stream_information/widgets/daily_stream_type_breakdown_graph.dart b/lib/modules/tautulli/modules/graphs_stream_information/widgets/daily_stream_type_breakdown_graph.dart index 3131ca23..b62d7b76 100644 --- a/lib/modules/tautulli/modules/graphs_stream_information/widgets/daily_stream_type_breakdown_graph.dart +++ b/lib/modules/tautulli/modules/graphs_stream_information/widgets/daily_stream_type_breakdown_graph.dart @@ -7,14 +7,14 @@ import 'package:tautulli/tautulli.dart'; class TautulliGraphsDailyStreamTypeBreakdownGraph extends StatelessWidget { @override - Widget build(BuildContext context) => Selector>( + Widget build(BuildContext context) => Selector>( selector: (_, state) => state.dailyStreamTypeBreakdownGraph, builder: (context, future, _) => FutureBuilder( future: future, builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( 'TautulliGraphsDailyStreamTypeBreakdownGraph', '_body', 'Unable to fetch Tautulli graph data: getPlaysByDate', diff --git a/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_platform_stream_type_graph.dart b/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_platform_stream_type_graph.dart index 341b6a15..c5ba10ed 100644 --- a/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_platform_stream_type_graph.dart +++ b/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_platform_stream_type_graph.dart @@ -6,14 +6,14 @@ import 'package:tautulli/tautulli.dart'; class TautulliGraphsPlayCountByPlatformStreamTypeGraph extends StatelessWidget { @override - Widget build(BuildContext context) => Selector>( + Widget build(BuildContext context) => Selector>( selector: (_, state) => state.playCountByPlatformStreamTypeGraph, builder: (context, future, _) => FutureBuilder( future: future, builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( 'TautulliGraphsPlayCountByPlatformStreamTypeGraph', '_body', 'Unable to fetch Tautulli graph data: getStreamTypeByTopTenPlatforms', diff --git a/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_source_resolution_graph.dart b/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_source_resolution_graph.dart index d3d21210..7c7ad8de 100644 --- a/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_source_resolution_graph.dart +++ b/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_source_resolution_graph.dart @@ -6,14 +6,14 @@ import 'package:tautulli/tautulli.dart'; class TautulliGraphsPlayCountBySourceResolutionGraph extends StatelessWidget { @override - Widget build(BuildContext context) => Selector>( + Widget build(BuildContext context) => Selector>( selector: (_, state) => state.playCountBySourceResolutionGraph, builder: (context, future, _) => FutureBuilder( future: future, builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( 'TautulliGraphsPlayCountBySourceResolutionGraph', '_body', 'Unable to fetch Tautulli graph data: getPlaysBySourceResolution', diff --git a/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_stream_resolution_graph.dart b/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_stream_resolution_graph.dart index c5a1dc87..f8767063 100644 --- a/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_stream_resolution_graph.dart +++ b/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_stream_resolution_graph.dart @@ -6,14 +6,14 @@ import 'package:tautulli/tautulli.dart'; class TautulliGraphsPlayCountByStreamResolutionGraph extends StatelessWidget { @override - Widget build(BuildContext context) => Selector>( + Widget build(BuildContext context) => Selector>( selector: (_, state) => state.playCountByStreamResolutionGraph, builder: (context, future, _) => FutureBuilder( future: future, builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( 'TautulliGraphsPlayCountByStreamResolutionGraph', '_body', 'Unable to fetch Tautulli graph data: getPlaysByStreamResolution', diff --git a/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_user_stream_type_graph.dart b/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_user_stream_type_graph.dart index f6a4ca21..83f317e0 100644 --- a/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_user_stream_type_graph.dart +++ b/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_user_stream_type_graph.dart @@ -6,14 +6,14 @@ import 'package:tautulli/tautulli.dart'; class TautulliGraphsPlayCountByUserStreamTypeGraph extends StatelessWidget { @override - Widget build(BuildContext context) => Selector>( + Widget build(BuildContext context) => Selector>( selector: (_, state) => state.playCountByUserStreamTypeGraph, builder: (context, future, _) => FutureBuilder( future: future, builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( 'TautulliGraphsPlayCountByUserStreamTypeGraph', '_body', 'Unable to fetch Tautulli graph data: getStreamTypeByTopTenUsers', diff --git a/lib/modules/tautulli/modules/history/route.dart b/lib/modules/tautulli/modules/history/route.dart index 3cd699d8..ad7ad184 100644 --- a/lib/modules/tautulli/modules/history/route.dart +++ b/lib/modules/tautulli/modules/history/route.dart @@ -21,9 +21,8 @@ class _State extends State with AutomaticKeepAliveClientMi bool get wantKeepAlive => true; Future _refresh() async { - TautulliState _state = Provider.of(context, listen: false); - _state.resetHistory(); - await _state.history; + context.read().resetHistory(); + await context.read().history; } @override @@ -51,7 +50,7 @@ class _State extends State with AutomaticKeepAliveClientMi builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( 'TautulliHistoryRoute', '_body', 'Unable to fetch Tautulli history', diff --git a/lib/modules/tautulli/modules/history/widgets/history_tile.dart b/lib/modules/tautulli/modules/history/widgets/history_tile.dart index dffaaef8..36aec250 100644 --- a/lib/modules/tautulli/modules/history/widgets/history_tile.dart +++ b/lib/modules/tautulli/modules/history/widgets/history_tile.dart @@ -28,18 +28,17 @@ class TautulliHistoryTile extends StatelessWidget { onTap: () async => _onTap(context), ), decoration: LSCardBackground( - darken: true, - uri: Provider.of(context, listen: false).getImageURLFromRatingKey( + uri: context.watch().getImageURLFromRatingKey( history.grandparentRatingKey ?? history.parentRatingKey ?? history.ratingKey ?? '', width: MediaQuery.of(context).size.width.truncate(), ), - headers: Provider.of(context, listen: false).headers.cast(), + headers: context.watch().headers.cast(), ), ); Widget _poster(BuildContext context) => LSNetworkImage( - url: Provider.of(context, listen: false).getImageURLFromPath(history.thumb), - headers: Provider.of(context, listen: false).headers.cast(), + url: context.watch().getImageURLFromPath(history.thumb), + headers: context.watch().headers.cast(), height: _imageDimension, width: _imageDimension/1.5, placeholder: 'assets/images/sonarr/noseriesposter.png', diff --git a/lib/modules/tautulli/modules/history_details/route.dart b/lib/modules/tautulli/modules/history_details/route.dart index 9019323f..54f034d9 100644 --- a/lib/modules/tautulli/modules/history_details/route.dart +++ b/lib/modules/tautulli/modules/history_details/route.dart @@ -12,13 +12,12 @@ class TautulliHistoryDetailsRouter { @required int ratingKey, int referenceId, int sessionKey, - }) async => TautulliRouter.router.navigateTo( + }) async => LunaRouter.router.navigateTo( context, route(ratingKey: ratingKey, referenceId: referenceId, sessionKey: sessionKey), ); static String route({ - String profile, @required int ratingKey, int referenceId, int sessionKey, @@ -26,24 +25,13 @@ class TautulliHistoryDetailsRouter { String _route = '/tautulli'; if(referenceId != null) _route = '/tautulli/history/details/$ratingKey/referenceid/$referenceId'; if(sessionKey != null) _route = '/tautulli/history/details/$ratingKey/sessionkey/$sessionKey'; - return profile != null ? _route + '/$profile' : _route; + return _route; } static void defineRoutes(Router router) { - router.define( - ROUTE_NAME + '/:profile', - handler: Handler(handlerFunc: (context, params) => _TautulliHistoryDetailsRoute( - profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null, - ratingKey: int.tryParse(params['ratingkey'][0]), - sessionKey: params['key'][0] == 'sessionkey' ? int.tryParse(params['value'][0]) : null, - referenceId: params['key'][0] == 'referenceid' ? int.tryParse(params['value'][0]) : null, - )), - transitionType: LunaRouter.transitionType, - ); router.define( ROUTE_NAME, handler: Handler(handlerFunc: (context, params) => _TautulliHistoryDetailsRoute( - profile: null, ratingKey: int.tryParse(params['ratingkey'][0]), sessionKey: params['key'][0] == 'sessionkey' ? int.tryParse(params['value'][0]) : null, referenceId: params['key'][0] == 'referenceid' ? int.tryParse(params['value'][0]) : null, @@ -56,14 +44,12 @@ class TautulliHistoryDetailsRouter { } class _TautulliHistoryDetailsRoute extends StatefulWidget { - final String profile; final int ratingKey; final int sessionKey; final int referenceId; _TautulliHistoryDetailsRoute({ Key key, - @required this.profile, @required this.ratingKey, this.sessionKey, this.referenceId, @@ -84,16 +70,14 @@ class _State extends State<_TautulliHistoryDetailsRoute> { } Future _refresh() async { - TautulliState _global = Provider.of(context, listen: false); - TautulliLocalState _local = Provider.of(context, listen: false); - _local.setHistory( + context.read().setIndividualHistory( widget.ratingKey, - _global.api.history.getHistory( + context.read().api.history.getHistory( length: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, ratingKey: widget.ratingKey, ), ); - await _local.history[widget.ratingKey]; + await context.read().individualHistory[widget.ratingKey]; } @override @@ -103,8 +87,10 @@ class _State extends State<_TautulliHistoryDetailsRoute> { body: _body, ); - Widget get _appBar => LSAppBar( + Widget get _appBar => LunaAppBar( + context: context, title: 'History Details', + popUntil: TautulliConstants.MODULE_MAP.route, actions: [ TautulliHistoryDetailsUser(ratingKey: widget.ratingKey, sessionKey: widget.sessionKey, referenceId: widget.referenceId), TautulliHistoryDetailsMetadata(ratingKey: widget.ratingKey, sessionKey: widget.sessionKey, referenceId: widget.referenceId), @@ -115,11 +101,11 @@ class _State extends State<_TautulliHistoryDetailsRoute> { refreshKey: _refreshKey, onRefresh: _refresh, child: FutureBuilder( - future: Provider.of(context).history[widget.ratingKey], + future: context.watch().individualHistory[widget.ratingKey], builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( '_TautulliHistoryDetailsRoute', '_shared', 'Unable to pull Tautulli history session', diff --git a/lib/modules/tautulli/modules/history_details/widgets/metadata.dart b/lib/modules/tautulli/modules/history_details/widgets/metadata.dart index ca7ff690..a2cb62cc 100644 --- a/lib/modules/tautulli/modules/history_details/widgets/metadata.dart +++ b/lib/modules/tautulli/modules/history_details/widgets/metadata.dart @@ -17,7 +17,7 @@ class TautulliHistoryDetailsMetadata extends StatelessWidget { @override Widget build(BuildContext context) => FutureBuilder( - future: Provider.of(context).history[ratingKey], + future: context.watch().individualHistory[ratingKey], builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) return Container(); if(snapshot.hasData) { diff --git a/lib/modules/tautulli/modules/history_details/widgets/user.dart b/lib/modules/tautulli/modules/history_details/widgets/user.dart index 0cd2394c..f690d8a5 100644 --- a/lib/modules/tautulli/modules/history_details/widgets/user.dart +++ b/lib/modules/tautulli/modules/history_details/widgets/user.dart @@ -17,7 +17,7 @@ class TautulliHistoryDetailsUser extends StatelessWidget { @override Widget build(BuildContext context) => FutureBuilder( - future: Provider.of(context).history[ratingKey], + future: context.watch().individualHistory[ratingKey], builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) return Container(); if(snapshot.hasData) { diff --git a/lib/modules/tautulli/modules/ipaddress_details/route.dart b/lib/modules/tautulli/modules/ipaddress_details/route.dart index d1ace3af..d7d1136b 100644 --- a/lib/modules/tautulli/modules/ipaddress_details/route.dart +++ b/lib/modules/tautulli/modules/ipaddress_details/route.dart @@ -10,33 +10,18 @@ class TautulliIPAddressDetailsRouter { static Future navigateTo(BuildContext context, { @required String ip, - }) async => TautulliRouter.router.navigateTo( + }) async => LunaRouter.router.navigateTo( context, route(ip: ip), ); - static String route({ - @required String ip, - String profile, - }) => [ - ROUTE_NAME.replaceFirst(':ipaddress', ip ?? '0'), - if(profile != null) '/$profile', - ].join(); + static String route({ @required String ip }) => ROUTE_NAME.replaceFirst(':ipaddress', ip ?? '0'); static void defineRoutes(Router router) { router.define( ROUTE_NAME, handler: Handler(handlerFunc: (context, params) => _TautulliIPAddressRoute( ipAddress: params['ipaddress'] != null && params['ipaddress'].length != 0 ? params['ipaddress'][0] : null, - profile: null, - )), - transitionType: LunaRouter.transitionType, - ); - router.define( - ROUTE_NAME + '/:profile', - handler: Handler(handlerFunc: (context, params) => _TautulliIPAddressRoute( - ipAddress: params['ipaddress'] != null && params['ipaddress'].length != 0 ? params['ipaddress'][0] : null, - profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null, )), transitionType: LunaRouter.transitionType, ); @@ -46,13 +31,11 @@ class TautulliIPAddressDetailsRouter { } class _TautulliIPAddressRoute extends StatefulWidget { - final String profile; final String ipAddress; _TautulliIPAddressRoute({ - @required this.profile, - @required this.ipAddress, Key key, + @required this.ipAddress, }) : super(key: key); @override @@ -71,13 +54,12 @@ class _State extends State<_TautulliIPAddressRoute> { } Future _refresh() async { - TautulliLocalState _state = Provider.of(context, listen: false); - _state.fetchGeolocationInformation(context, widget.ipAddress); - _state.fetchWHOISInformation(context, widget.ipAddress); + context.read().fetchGeolocationInformation(widget.ipAddress); + context.read().fetchWHOISInformation(widget.ipAddress); setState(() => _initialLoad = true); await Future.wait([ - _state.geolocationInformation[widget.ipAddress], - _state.whoisInformation[widget.ipAddress], + context.read().geolocationInformation[widget.ipAddress], + context.read().whoisInformation[widget.ipAddress], ]); } @@ -88,17 +70,21 @@ class _State extends State<_TautulliIPAddressRoute> { body: _initialLoad ? _body : LSLoader(), ); - Widget get _appBar => LSAppBar(title: 'IP Address Details'); + Widget get _appBar => LunaAppBar( + context: context, + title: 'IP Address Details', + popUntil: '/tautulli', + ); Widget get _body => FutureBuilder( future: Future.wait([ - Provider.of(context).geolocationInformation[widget.ipAddress], - Provider.of(context).whoisInformation[widget.ipAddress], + context.watch().geolocationInformation[widget.ipAddress], + context.watch().whoisInformation[widget.ipAddress], ]), builder: (context, AsyncSnapshot> snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( '_TautulliIPAddressRoute', '_body', 'Unable to fetch Tautulli IP address information', diff --git a/lib/modules/tautulli/modules/libraries/route.dart b/lib/modules/tautulli/modules/libraries/route.dart index 03341f00..5a649583 100644 --- a/lib/modules/tautulli/modules/libraries/route.dart +++ b/lib/modules/tautulli/modules/libraries/route.dart @@ -8,29 +8,17 @@ import 'package:tautulli/tautulli.dart'; class TautulliLibrariesRouter { static const String ROUTE_NAME = '/tautulli/libraries/list'; - static Future navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( context, route(), ); - static String route({ String profile }) => [ - ROUTE_NAME, - if(profile != null) '/$profile', - ].join(); + static String route() => ROUTE_NAME; static void defineRoutes(Router router) { router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => _TautulliLibrariesRoute( - profile: null, - )), - transitionType: LunaRouter.transitionType, - ); - router.define( - ROUTE_NAME + '/:profile', - handler: Handler(handlerFunc: (context, params) => _TautulliLibrariesRoute( - profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null, - )), + handler: Handler(handlerFunc: (context, params) => _TautulliLibrariesRoute()), transitionType: LunaRouter.transitionType, ); } @@ -39,13 +27,6 @@ class TautulliLibrariesRouter { } class _TautulliLibrariesRoute extends StatefulWidget { - final String profile; - - _TautulliLibrariesRoute({ - Key key, - @required this.profile, - }) : super(key: key); - @override State<_TautulliLibrariesRoute> createState() => _State(); } @@ -55,9 +36,8 @@ class _State extends State<_TautulliLibrariesRoute> { final GlobalKey _refreshKey = GlobalKey(); Future _refresh() async { - TautulliLocalState _state = Provider.of(context, listen: false); - _state.resetLibrariesTable(context); - await _state.librariesTable; + context.read().resetLibrariesTable(); + await context.read().librariesTable; } @override @@ -73,19 +53,23 @@ class _State extends State<_TautulliLibrariesRoute> { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Libraries'); + Widget get _appBar => LunaAppBar( + context: context, + title: 'Libraries', + popUntil: '/tautulli', + ); Widget get _body => LSRefreshIndicator( onRefresh: _refresh, refreshKey: _refreshKey, - child: Selector>( + child: Selector>( selector: (_, state) => state.librariesTable, builder: (context, future, _) => FutureBuilder( future: future, builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( '_TautulliLibrariesRoute', '_body', 'Unable to fetch Tautulli libraries table', diff --git a/lib/modules/tautulli/modules/libraries/widgets/library_tile.dart b/lib/modules/tautulli/modules/libraries/widgets/library_tile.dart index 65bae552..7e53d32d 100644 --- a/lib/modules/tautulli/modules/libraries/widgets/library_tile.dart +++ b/lib/modules/tautulli/modules/libraries/widgets/library_tile.dart @@ -29,7 +29,7 @@ class TautulliLibrariesLibraryTile extends StatelessWidget { TextSpan(text: '${library.duration.lsDuration_fullTimestamp()}\n'), TextSpan( style: TextStyle( - color: LSColors.accent, + color: LunaColours.accent, fontWeight: FontWeight.w600, ), text: '${DateTime.now().lsDateTime_ageString(library.lastAccessed)}', @@ -41,9 +41,8 @@ class TautulliLibrariesLibraryTile extends StatelessWidget { ), padContent: true, decoration: LSCardBackground( - uri: Provider.of(context, listen: false).getImageURLFromPath(library.thumb), - headers: Provider.of(context, listen: false).headers, - darken: true, + uri: context.watch().getImageURLFromPath(library.thumb), + headers: context.watch().headers, ), onTap: () async => _onTap(context), ); diff --git a/lib/modules/tautulli/modules/libraries_details/route.dart b/lib/modules/tautulli/modules/libraries_details/route.dart index 9e629457..c38c3f90 100644 --- a/lib/modules/tautulli/modules/libraries_details/route.dart +++ b/lib/modules/tautulli/modules/libraries_details/route.dart @@ -8,39 +8,20 @@ class TautulliLibrariesDetailsRouter { static Future navigateTo(BuildContext context, { @required int sectionId, - }) async => TautulliRouter.router.navigateTo( + }) async => LunaRouter.router.navigateTo( context, route(sectionId: sectionId), ); - static String route({ - String profile, - @required int sectionId, - }) => [ - ROUTE_NAME.replaceFirst(':sectionid', sectionId?.toString() ?? '-1'), - if(profile != null) '/$profile', - ].join(); + static String route({ @required int sectionId }) => ROUTE_NAME.replaceFirst(':sectionid', sectionId?.toString() ?? '-1'); static void defineRoutes(Router router) { - router.define( - ROUTE_NAME + '/:profile', - handler: Handler(handlerFunc: (context, params) => _TautulliLibrariesDetailsRoute( - sectionId: params['sectionid'] != null && params['sectionid'].length != 0 - ? int.tryParse(params['sectionid'][0]) ?? -1 - : -1, - profile: params['profile'] != null && params['profile'].length != 0 - ? params['profile'][0] - : null, - )), - transitionType: LunaRouter.transitionType, - ); router.define( ROUTE_NAME, handler: Handler(handlerFunc: (context, params) => _TautulliLibrariesDetailsRoute( sectionId: params['sectionid'] != null && params['sectionid'].length != 0 ? int.tryParse(params['sectionid'][0]) ?? -1 : -1, - profile: null, )), transitionType: LunaRouter.transitionType, ); @@ -50,13 +31,11 @@ class TautulliLibrariesDetailsRouter { } class _TautulliLibrariesDetailsRoute extends StatefulWidget { - final String profile; final int sectionId; _TautulliLibrariesDetailsRoute({ - @required this.profile, - @required this.sectionId, Key key, + @required this.sectionId, }) : super(key: key); @override @@ -81,7 +60,11 @@ class _State extends State<_TautulliLibrariesDetailsRoute> { bottomNavigationBar: TautulliLibrariesDetailsNavigationBar(pageController: _pageController), ); - Widget get _appBar => LSAppBar(title: 'Library Details'); + Widget get _appBar => LunaAppBar( + context: context, + title: 'Library Details', + popUntil: '/tautulli', + ); Widget get _body => PageView( controller: _pageController, diff --git a/lib/modules/tautulli/modules/libraries_details/widgets/information.dart b/lib/modules/tautulli/modules/libraries_details/widgets/information.dart index 418fe7e8..d440b3b9 100644 --- a/lib/modules/tautulli/modules/libraries_details/widgets/information.dart +++ b/lib/modules/tautulli/modules/libraries_details/widgets/information.dart @@ -25,13 +25,12 @@ class _State extends State with AutomaticKe bool get wantKeepAlive => true; Future _refresh() async { - TautulliLocalState _state = Provider.of(context, listen: false); - _state.resetLibrariesTable(context); - _state.fetchLibraryWatchTimeStats(context, widget.sectionId); + context.read().resetLibrariesTable(); + context.read().fetchLibraryWatchTimeStats(widget.sectionId); setState(() => _initialLoad = true); await Future.wait([ - _state.librariesTable, - _state.libraryWatchTimeStats[widget.sectionId], + context.read().librariesTable, + context.read().libraryWatchTimeStats[widget.sectionId], ]); } @@ -55,8 +54,8 @@ class _State extends State with AutomaticKe onRefresh: _refresh, child: FutureBuilder( future: Future.wait([ - Provider.of(context).librariesTable, - Provider.of(context).libraryWatchTimeStats[widget.sectionId], + context.watch().librariesTable, + context.watch().libraryWatchTimeStats[widget.sectionId], ]), builder: (context, AsyncSnapshot> snapshot) { if(snapshot.hasError) return LSErrorMessage(onTapHandler: () async => _refreshKey.currentState.show()); diff --git a/lib/modules/tautulli/modules/libraries_details/widgets/user_stats.dart b/lib/modules/tautulli/modules/libraries_details/widgets/user_stats.dart index a124af8c..41ddde2c 100644 --- a/lib/modules/tautulli/modules/libraries_details/widgets/user_stats.dart +++ b/lib/modules/tautulli/modules/libraries_details/widgets/user_stats.dart @@ -24,9 +24,8 @@ class _State extends State with AutomaticKeep bool get wantKeepAlive => true; Future _refresh() async { - TautulliLocalState _state = Provider.of(context, listen: false); - _state.fetchLibraryUserStats(context, widget.sectionId); - await _state.libraryUserStats[widget.sectionId]; + context.read().fetchLibraryUserStats(widget.sectionId); + await context.read().libraryUserStats[widget.sectionId]; } @override @@ -48,7 +47,7 @@ class _State extends State with AutomaticKeep refreshKey: _refreshKey, onRefresh: _refresh, child: FutureBuilder( - future: Provider.of(context).libraryUserStats[widget.sectionId], + future: context.watch().libraryUserStats[widget.sectionId], builder: (context, AsyncSnapshot> snapshot) { if(snapshot.hasError) return LSErrorMessage(onTapHandler: () async => _refreshKey.currentState.show()); if(snapshot.hasData) return snapshot.data.length == 0 ? _noStatsFound : _list(userStats: snapshot.data); diff --git a/lib/modules/tautulli/modules/libraries_details/widgets/user_stats_tile.dart b/lib/modules/tautulli/modules/libraries_details/widgets/user_stats_tile.dart index 330cc977..7a3c2228 100644 --- a/lib/modules/tautulli/modules/libraries_details/widgets/user_stats_tile.dart +++ b/lib/modules/tautulli/modules/libraries_details/widgets/user_stats_tile.dart @@ -30,8 +30,8 @@ class TautulliLibrariesDetailsUserStatsTile extends StatelessWidget { ); Widget _userThumb(BuildContext context) => LSNetworkImage( - url: Provider.of(context, listen: false).getImageURLFromPath(user.userThumb), - headers: Provider.of(context, listen: false).headers.cast(), + url: context.watch().getImageURLFromPath(user.userThumb), + headers: context.watch().headers.cast(), placeholder: 'assets/images/tautulli/nouserthumb.png', height: _imageDimension, width: _imageDimension, diff --git a/lib/modules/tautulli/modules/logs/route.dart b/lib/modules/tautulli/modules/logs/route.dart index c4e769a9..82d52e91 100644 --- a/lib/modules/tautulli/modules/logs/route.dart +++ b/lib/modules/tautulli/modules/logs/route.dart @@ -6,29 +6,17 @@ import 'package:lunasea/modules/tautulli.dart'; class TautulliLogsRouter { static const String ROUTE_NAME = '/tautulli/logs/list'; - static Future navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( context, route(), ); - static String route({ String profile }) => [ - ROUTE_NAME, - if(profile != null) '/$profile', - ].join(); + static String route() => ROUTE_NAME; static void defineRoutes(Router router) { router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => _TautulliLogsRoute( - profile: null, - )), - transitionType: LunaRouter.transitionType, - ); - router.define( - ROUTE_NAME + '/:profile', - handler: Handler(handlerFunc: (context, params) => _TautulliLogsRoute( - profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null, - )), + handler: Handler(handlerFunc: (context, params) => _TautulliLogsRoute()), transitionType: LunaRouter.transitionType, ); } @@ -37,13 +25,6 @@ class TautulliLogsRouter { } class _TautulliLogsRoute extends StatefulWidget { - final String profile; - - _TautulliLogsRoute({ - Key key, - @required this.profile, - }) : super(key: key); - @override State createState() => _State(); } @@ -58,7 +39,11 @@ class _State extends State<_TautulliLogsRoute> { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Tautulli Logs'); + Widget get _appBar => LunaAppBar( + context: context, + title: 'Logs', + popUntil: '/tautulli', + ); Widget get _body => LSListView( children: [ diff --git a/lib/modules/tautulli/modules/logs/widgets/logins_tile.dart b/lib/modules/tautulli/modules/logs/widgets/logins_tile.dart index 6621b9dc..40245641 100644 --- a/lib/modules/tautulli/modules/logs/widgets/logins_tile.dart +++ b/lib/modules/tautulli/modules/logs/widgets/logins_tile.dart @@ -9,7 +9,7 @@ class TautulliLogsLoginsTile extends StatelessWidget { subtitle: LSSubtitle(text: 'Tautulli Login Logs'), trailing: LSIconButton( icon: Icons.vpn_key, - color: LSColors.list(0), + color: LunaColours.list(0), ), onTap: () async => _onTap(context), ); diff --git a/lib/modules/tautulli/modules/logs/widgets/newsletters_tile.dart b/lib/modules/tautulli/modules/logs/widgets/newsletters_tile.dart index 581bfcae..187097f4 100644 --- a/lib/modules/tautulli/modules/logs/widgets/newsletters_tile.dart +++ b/lib/modules/tautulli/modules/logs/widgets/newsletters_tile.dart @@ -9,7 +9,7 @@ class TautulliLogsNewslettersTile extends StatelessWidget { subtitle: LSSubtitle(text: 'Tautulli Newsletter Logs'), trailing: LSIconButton( icon: Icons.email, - color: LSColors.list(1), + color: LunaColours.list(1), ), onTap: () async => _onTap(context), ); diff --git a/lib/modules/tautulli/modules/logs/widgets/notifications_tile.dart b/lib/modules/tautulli/modules/logs/widgets/notifications_tile.dart index 53a7437e..a43b9907 100644 --- a/lib/modules/tautulli/modules/logs/widgets/notifications_tile.dart +++ b/lib/modules/tautulli/modules/logs/widgets/notifications_tile.dart @@ -9,7 +9,7 @@ class TautulliLogsNotificationsTile extends StatelessWidget { subtitle: LSSubtitle(text: 'Tautulli Notification Logs'), trailing: LSIconButton( icon: Icons.notifications, - color: LSColors.list(2), + color: LunaColours.list(2), ), onTap: () async => _onTap(context), ); diff --git a/lib/modules/tautulli/modules/logs/widgets/plex_media_scanner.dart b/lib/modules/tautulli/modules/logs/widgets/plex_media_scanner.dart index 38e4d3f6..93eb0f58 100644 --- a/lib/modules/tautulli/modules/logs/widgets/plex_media_scanner.dart +++ b/lib/modules/tautulli/modules/logs/widgets/plex_media_scanner.dart @@ -9,7 +9,7 @@ class TautulliLogsPlexMediaScannerTile extends StatelessWidget { subtitle: LSSubtitle(text: 'Plex Media Scanner Logs'), trailing: LSIconButton( icon: Icons.scanner, - color: LSColors.list(3), + color: LunaColours.list(3), ), onTap: () async => _onTap(context), ); diff --git a/lib/modules/tautulli/modules/logs/widgets/plex_media_server_tile.dart b/lib/modules/tautulli/modules/logs/widgets/plex_media_server_tile.dart index 82a317d0..08cf21b9 100644 --- a/lib/modules/tautulli/modules/logs/widgets/plex_media_server_tile.dart +++ b/lib/modules/tautulli/modules/logs/widgets/plex_media_server_tile.dart @@ -9,7 +9,7 @@ class TautulliLogsPlexMediaServerTile extends StatelessWidget { subtitle: LSSubtitle(text: 'Plex Media Server Logs'), trailing: LSIconButton( icon: CustomIcons.plex, - color: LSColors.list(4), + color: LunaColours.list(4), ), onTap: () async => _onTap(context), ); diff --git a/lib/modules/tautulli/modules/logs/widgets/tautulli_tile.dart b/lib/modules/tautulli/modules/logs/widgets/tautulli_tile.dart index 4ef75ed6..cf38e911 100644 --- a/lib/modules/tautulli/modules/logs/widgets/tautulli_tile.dart +++ b/lib/modules/tautulli/modules/logs/widgets/tautulli_tile.dart @@ -9,7 +9,7 @@ class TautulliLogsTautulliTile extends StatelessWidget { subtitle: LSSubtitle(text: 'Tautulli Logs'), trailing: LSIconButton( icon: CustomIcons.tautulli, - color: LSColors.list(5), + color: LunaColours.list(5), ), onTap: () async => _onTap(context), ); diff --git a/lib/modules/tautulli/modules/logs_logins/route.dart b/lib/modules/tautulli/modules/logs_logins/route.dart index bfead7e4..aaa9f623 100644 --- a/lib/modules/tautulli/modules/logs_logins/route.dart +++ b/lib/modules/tautulli/modules/logs_logins/route.dart @@ -8,29 +8,17 @@ import 'package:tautulli/tautulli.dart'; class TautulliLogsLoginsRouter { static const String ROUTE_NAME = '/tautulli/logs/logins'; - static Future navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( context, route(), ); - static String route({ String profile }) => [ - ROUTE_NAME, - if(profile != null) '/$profile', - ].join(); + static String route() => ROUTE_NAME; static void defineRoutes(Router router) { router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => _TautulliLogsLoginsRoute( - profile: null, - )), - transitionType: LunaRouter.transitionType, - ); - router.define( - ROUTE_NAME + '/:profile', - handler: Handler(handlerFunc: (context, params) => _TautulliLogsLoginsRoute( - profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null, - )), + handler: Handler(handlerFunc: (context, params) => _TautulliLogsLoginsRoute()), transitionType: LunaRouter.transitionType, ); } @@ -39,13 +27,6 @@ class TautulliLogsLoginsRouter { } class _TautulliLogsLoginsRoute extends StatefulWidget { - final String profile; - - _TautulliLogsLoginsRoute({ - Key key, - @required this.profile, - }) : super(key: key); - @override State createState() => _State(); } @@ -55,9 +36,8 @@ class _State extends State<_TautulliLogsLoginsRoute> { final GlobalKey _refreshKey = GlobalKey(); Future _refresh() async { - TautulliLocalState _state = Provider.of(context, listen: false); - _state.resetLoginLogs(context); - await _state.loginLogs; + context.read().resetLoginLogs(); + await context.read().loginLogs; } @override @@ -73,19 +53,23 @@ class _State extends State<_TautulliLogsLoginsRoute> { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Login Logs'); + Widget get _appBar => LunaAppBar( + context: context, + title: 'Login Logs', + popUntil: '/tautulli', + ); Widget get _body => LSRefreshIndicator( onRefresh: _refresh, refreshKey: _refreshKey, - child: Selector>( + child: Selector>( selector: (_, state) => state.loginLogs, builder: (context, logs, _) => FutureBuilder( future: logs, builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( '_TautulliLogsLoginsRoute', '_body', 'Unable to fetch Tautulli login logs', diff --git a/lib/modules/tautulli/modules/logs_logins/widgets/log_tile.dart b/lib/modules/tautulli/modules/logs_logins/widgets/log_tile.dart index 842a559c..4598bab1 100644 --- a/lib/modules/tautulli/modules/logs_logins/widgets/log_tile.dart +++ b/lib/modules/tautulli/modules/logs_logins/widgets/log_tile.dart @@ -32,9 +32,9 @@ class TautulliLogsLoginsLogTile extends StatelessWidget { TextSpan( text: LunaSeaDatabaseValue.USE_24_HOUR_TIME.data ? DateFormat('MMMM dd, yyyy ${Constants.TEXT_EMDASH} HH:mm').format(login.timestamp) - : DateFormat('MMMM dd, yyyy ${Constants.TEXT_EMDASH} KK:mm a').format(login.timestamp), + : DateFormat('MMMM dd, yyyy ${Constants.TEXT_EMDASH} hh:mm a').format(login.timestamp), style: TextStyle( - color: LSColors.accent, + color: LunaColours.accent, fontWeight: FontWeight.w600, ), ), @@ -49,7 +49,7 @@ class TautulliLogsLoginsLogTile extends StatelessWidget { children: [ LSIconButton( icon: login.success ? Icons.check_circle : Icons.cancel, - color: login.success ? Colors.white : LSColors.red, + color: login.success ? Colors.white : LunaColours.red, ), ], crossAxisAlignment: CrossAxisAlignment.center, diff --git a/lib/modules/tautulli/modules/logs_newsletters/route.dart b/lib/modules/tautulli/modules/logs_newsletters/route.dart index 80dead75..b9800420 100644 --- a/lib/modules/tautulli/modules/logs_newsletters/route.dart +++ b/lib/modules/tautulli/modules/logs_newsletters/route.dart @@ -8,29 +8,17 @@ import 'package:tautulli/tautulli.dart'; class TautulliLogsNewslettersRouter { static const String ROUTE_NAME = '/tautulli/logs/newsletters'; - static Future navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( context, route(), ); - static String route({ String profile }) => [ - ROUTE_NAME, - if(profile != null) '/$profile', - ].join(); + static String route() => ROUTE_NAME; static void defineRoutes(Router router) { router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => _TautulliLogsNewslettersRoute( - profile: null, - )), - transitionType: LunaRouter.transitionType, - ); - router.define( - ROUTE_NAME + '/:profile', - handler: Handler(handlerFunc: (context, params) => _TautulliLogsNewslettersRoute( - profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null, - )), + handler: Handler(handlerFunc: (context, params) => _TautulliLogsNewslettersRoute()), transitionType: LunaRouter.transitionType, ); } @@ -39,13 +27,6 @@ class TautulliLogsNewslettersRouter { } class _TautulliLogsNewslettersRoute extends StatefulWidget { - final String profile; - - _TautulliLogsNewslettersRoute({ - Key key, - @required this.profile, - }) : super(key: key); - @override State createState() => _State(); } @@ -55,9 +36,8 @@ class _State extends State<_TautulliLogsNewslettersRoute> { final GlobalKey _refreshKey = GlobalKey(); Future _refresh() async { - TautulliLocalState _state = Provider.of(context, listen: false); - _state.resetNewsletterLogs(context); - await _state.newsletterLogs; + context.read().resetNewsletterLogs(); + await context.read().newsletterLogs; } @override @@ -73,19 +53,23 @@ class _State extends State<_TautulliLogsNewslettersRoute> { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Newsletter Logs'); + Widget get _appBar => LunaAppBar( + context: context, + title: 'Newsletter Logs', + popUntil: '/tautulli', + ); Widget get _body => LSRefreshIndicator( onRefresh: _refresh, refreshKey: _refreshKey, - child: Selector>( + child: Selector>( selector: (_, state) => state.newsletterLogs, builder: (context, logs, _) => FutureBuilder( future: logs, builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( '_TautulliLogsNewslettersRoute', '_body', 'Unable to fetch Tautulli newsletter logs', diff --git a/lib/modules/tautulli/modules/logs_newsletters/widgets/log_tile.dart b/lib/modules/tautulli/modules/logs_newsletters/widgets/log_tile.dart index db9dc081..972bf9a0 100644 --- a/lib/modules/tautulli/modules/logs_newsletters/widgets/log_tile.dart +++ b/lib/modules/tautulli/modules/logs_newsletters/widgets/log_tile.dart @@ -32,9 +32,9 @@ class TautulliLogsNewsletterLogTile extends StatelessWidget { TextSpan( text: LunaSeaDatabaseValue.USE_24_HOUR_TIME.data ? DateFormat('MMMM dd, yyyy ${Constants.TEXT_EMDASH} HH:mm').format(newsletter.timestamp) - : DateFormat('MMMM dd, yyyy ${Constants.TEXT_EMDASH} KK:mm a').format(newsletter.timestamp), + : DateFormat('MMMM dd, yyyy ${Constants.TEXT_EMDASH} hh:mm a').format(newsletter.timestamp), style: TextStyle( - color: LSColors.accent, + color: LunaColours.accent, fontWeight: FontWeight.w600, ), ), @@ -49,7 +49,7 @@ class TautulliLogsNewsletterLogTile extends StatelessWidget { children: [ LSIconButton( icon: newsletter.success ? Icons.check_circle : Icons.cancel, - color: newsletter.success ? Colors.white : LSColors.red, + color: newsletter.success ? Colors.white : LunaColours.red, ), ], crossAxisAlignment: CrossAxisAlignment.center, diff --git a/lib/modules/tautulli/modules/logs_notifications/route.dart b/lib/modules/tautulli/modules/logs_notifications/route.dart index 3ca63d19..c3da576e 100644 --- a/lib/modules/tautulli/modules/logs_notifications/route.dart +++ b/lib/modules/tautulli/modules/logs_notifications/route.dart @@ -8,29 +8,17 @@ import 'package:tautulli/tautulli.dart'; class TautulliLogsNotificationsRouter { static const String ROUTE_NAME = '/tautulli/logs/notifications'; - static Future navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( context, route(), ); - static String route({ String profile }) => [ - ROUTE_NAME, - if(profile != null) '/$profile', - ].join(); + static String route() => ROUTE_NAME; static void defineRoutes(Router router) { router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => _TautulliLogsNotificationsRoute( - profile: null, - )), - transitionType: LunaRouter.transitionType, - ); - router.define( - ROUTE_NAME + '/:profile', - handler: Handler(handlerFunc: (context, params) => _TautulliLogsNotificationsRoute( - profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null, - )), + handler: Handler(handlerFunc: (context, params) => _TautulliLogsNotificationsRoute()), transitionType: LunaRouter.transitionType, ); } @@ -39,13 +27,6 @@ class TautulliLogsNotificationsRouter { } class _TautulliLogsNotificationsRoute extends StatefulWidget { - final String profile; - - _TautulliLogsNotificationsRoute({ - Key key, - @required this.profile, - }) : super(key: key); - @override State createState() => _State(); } @@ -55,9 +36,8 @@ class _State extends State<_TautulliLogsNotificationsRoute> { final GlobalKey _refreshKey = GlobalKey(); Future _refresh() async { - TautulliLocalState _state = Provider.of(context, listen: false); - _state.resetNotificationLogs(context); - await _state.notificationLogs; + context.read().resetNotificationLogs(); + await context.read().notificationLogs; } @override @@ -73,19 +53,23 @@ class _State extends State<_TautulliLogsNotificationsRoute> { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Notification Logs'); + Widget get _appBar => LunaAppBar( + context: context, + title: 'Notification Logs', + popUntil: '/tautulli', + ); Widget get _body => LSRefreshIndicator( onRefresh: _refresh, refreshKey: _refreshKey, - child: Selector>( + child: Selector>( selector: (_, state) => state.notificationLogs, builder: (context, logs, _) => FutureBuilder( future: logs, builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( '_TautulliLogsNotificationsRoute', '_body', 'Unable to fetch Tautulli notification logs', diff --git a/lib/modules/tautulli/modules/logs_notifications/widgets/log_tile.dart b/lib/modules/tautulli/modules/logs_notifications/widgets/log_tile.dart index 8dba7528..6eaca0fc 100644 --- a/lib/modules/tautulli/modules/logs_notifications/widgets/log_tile.dart +++ b/lib/modules/tautulli/modules/logs_notifications/widgets/log_tile.dart @@ -32,9 +32,9 @@ class TautulliLogsNotificationLogTile extends StatelessWidget { TextSpan( text: LunaSeaDatabaseValue.USE_24_HOUR_TIME.data ? DateFormat('MMMM dd, yyyy ${Constants.TEXT_EMDASH} HH:mm').format(notification.timestamp) - : DateFormat('MMMM dd, yyyy ${Constants.TEXT_EMDASH} KK:mm a').format(notification.timestamp), + : DateFormat('MMMM dd, yyyy ${Constants.TEXT_EMDASH} hh:mm a').format(notification.timestamp), style: TextStyle( - color: LSColors.accent, + color: LunaColours.accent, fontWeight: FontWeight.w600, ), ), @@ -49,7 +49,7 @@ class TautulliLogsNotificationLogTile extends StatelessWidget { children: [ LSIconButton( icon: notification.success ? Icons.check_circle : Icons.cancel, - color: notification.success ? Colors.white : LSColors.red, + color: notification.success ? Colors.white : LunaColours.red, ), ], crossAxisAlignment: CrossAxisAlignment.center, diff --git a/lib/modules/tautulli/modules/logs_plex_media_scanner/route.dart b/lib/modules/tautulli/modules/logs_plex_media_scanner/route.dart index 6b15c97e..f4890191 100644 --- a/lib/modules/tautulli/modules/logs_plex_media_scanner/route.dart +++ b/lib/modules/tautulli/modules/logs_plex_media_scanner/route.dart @@ -8,29 +8,17 @@ import 'package:tautulli/tautulli.dart'; class TautulliLogsPlexMediaScannerRouter { static const String ROUTE_NAME = '/tautulli/logs/plexmediascanner'; - static Future navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( context, route(), ); - static String route({ String profile }) => [ - ROUTE_NAME, - if(profile != null) '/$profile', - ].join(); + static String route() => ROUTE_NAME; static void defineRoutes(Router router) { router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => _TautulliLogsPlexMediaScannerRoute( - profile: null, - )), - transitionType: LunaRouter.transitionType, - ); - router.define( - ROUTE_NAME + '/:profile', - handler: Handler(handlerFunc: (context, params) => _TautulliLogsPlexMediaScannerRoute( - profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null, - )), + handler: Handler(handlerFunc: (context, params) => _TautulliLogsPlexMediaScannerRoute()), transitionType: LunaRouter.transitionType, ); } @@ -39,13 +27,6 @@ class TautulliLogsPlexMediaScannerRouter { } class _TautulliLogsPlexMediaScannerRoute extends StatefulWidget { - final String profile; - - _TautulliLogsPlexMediaScannerRoute({ - Key key, - @required this.profile, - }) : super(key: key); - @override State createState() => _State(); } @@ -55,9 +36,8 @@ class _State extends State<_TautulliLogsPlexMediaScannerRoute> { final GlobalKey _refreshKey = GlobalKey(); Future _refresh() async { - TautulliLocalState _state = Provider.of(context, listen: false); - _state.resetPlexMediaScannerLogs(context); - await _state.plexMediaScannerLogs; + context.read().resetPlexMediaScannerLogs(); + await context.read().plexMediaScannerLogs; } @override @@ -73,19 +53,23 @@ class _State extends State<_TautulliLogsPlexMediaScannerRoute> { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Plex Media Scanner Logs'); + Widget get _appBar => LunaAppBar( + context: context, + title: 'Plex Media Scanner Logs', + popUntil: '/tautulli', + ); Widget get _body => LSRefreshIndicator( onRefresh: _refresh, refreshKey: _refreshKey, - child: Selector>>( + child: Selector>>( selector: (_, state) => state.plexMediaScannerLogs, builder: (context, logs, _) => FutureBuilder( future: logs, builder: (context, AsyncSnapshot> snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( '_TautulliLogsPlexMediaScannerRoute', '_body', 'Unable to fetch Tautulli plex media scanner logs', diff --git a/lib/modules/tautulli/modules/logs_plex_media_scanner/widgets/log_tile.dart b/lib/modules/tautulli/modules/logs_plex_media_scanner/widgets/log_tile.dart index 9479585a..ff634c98 100644 --- a/lib/modules/tautulli/modules/logs_plex_media_scanner/widgets/log_tile.dart +++ b/lib/modules/tautulli/modules/logs_plex_media_scanner/widgets/log_tile.dart @@ -37,7 +37,7 @@ class TautulliLogsPlexMediaScannerLogTile extends StatelessWidget { TextSpan( text: log.timestamp, style: TextStyle( - color: LSColors.accent, + color: LunaColours.accent, fontWeight: FontWeight.w600, ) ), @@ -65,11 +65,11 @@ class TautulliLogsPlexMediaScannerLogTile extends StatelessWidget { children: [ LSTextHighlighted( text: log.level, - bgColor: LSColors.blue, + bgColor: LunaColours.blue, ), LSTextHighlighted( text: log.timestamp, - bgColor: LSColors.accent, + bgColor: LunaColours.accent, ), ], ), diff --git a/lib/modules/tautulli/modules/logs_plex_media_server/route.dart b/lib/modules/tautulli/modules/logs_plex_media_server/route.dart index 014b3396..415d379a 100644 --- a/lib/modules/tautulli/modules/logs_plex_media_server/route.dart +++ b/lib/modules/tautulli/modules/logs_plex_media_server/route.dart @@ -8,29 +8,17 @@ import 'package:tautulli/tautulli.dart'; class TautulliLogsPlexMediaServerRouter { static const String ROUTE_NAME = '/tautulli/logs/plexmediaserver'; - static Future navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( context, route(), ); - static String route({ String profile }) => [ - ROUTE_NAME, - if(profile != null) '/$profile', - ].join(); + static String route() => ROUTE_NAME; static void defineRoutes(Router router) { router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => _TautulliLogsPlexMediaServerRoute( - profile: null, - )), - transitionType: LunaRouter.transitionType, - ); - router.define( - ROUTE_NAME + '/:profile', - handler: Handler(handlerFunc: (context, params) => _TautulliLogsPlexMediaServerRoute( - profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null, - )), + handler: Handler(handlerFunc: (context, params) => _TautulliLogsPlexMediaServerRoute()), transitionType: LunaRouter.transitionType, ); } @@ -39,13 +27,6 @@ class TautulliLogsPlexMediaServerRouter { } class _TautulliLogsPlexMediaServerRoute extends StatefulWidget { - final String profile; - - _TautulliLogsPlexMediaServerRoute({ - Key key, - @required this.profile, - }) : super(key: key); - @override State createState() => _State(); } @@ -55,9 +36,8 @@ class _State extends State<_TautulliLogsPlexMediaServerRoute> { final GlobalKey _refreshKey = GlobalKey(); Future _refresh() async { - TautulliLocalState _state = Provider.of(context, listen: false); - _state.resetPlexMediaServerLogs(context); - await _state.plexMediaServerLogs; + context.read().resetPlexMediaServerLogs(); + await context.read().plexMediaServerLogs; } @override @@ -73,19 +53,23 @@ class _State extends State<_TautulliLogsPlexMediaServerRoute> { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Plex Media Server Logs'); + Widget get _appBar => LunaAppBar( + context: context, + title: 'Plex Media Server Logs', + popUntil: '/tautulli', + ); Widget get _body => LSRefreshIndicator( onRefresh: _refresh, refreshKey: _refreshKey, - child: Selector>>( + child: Selector>>( selector: (_, state) => state.plexMediaServerLogs, builder: (context, logs, _) => FutureBuilder( future: logs, builder: (context, AsyncSnapshot> snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( '_TautulliLogsPlexMediaServerRoute', '_body', 'Unable to fetch Tautulli plex media server logs', diff --git a/lib/modules/tautulli/modules/logs_plex_media_server/widgets/log_tile.dart b/lib/modules/tautulli/modules/logs_plex_media_server/widgets/log_tile.dart index 0b7ef02c..96652dfc 100644 --- a/lib/modules/tautulli/modules/logs_plex_media_server/widgets/log_tile.dart +++ b/lib/modules/tautulli/modules/logs_plex_media_server/widgets/log_tile.dart @@ -37,7 +37,7 @@ class TautulliLogsPlexMediaServerLogTile extends StatelessWidget { TextSpan( text: log.timestamp, style: TextStyle( - color: LSColors.accent, + color: LunaColours.accent, fontWeight: FontWeight.w600, ) ), @@ -65,11 +65,11 @@ class TautulliLogsPlexMediaServerLogTile extends StatelessWidget { children: [ LSTextHighlighted( text: log.level, - bgColor: LSColors.blue, + bgColor: LunaColours.blue, ), LSTextHighlighted( text: log.timestamp, - bgColor: LSColors.accent, + bgColor: LunaColours.accent, ), ], ), diff --git a/lib/modules/tautulli/modules/logs_tautulli/route.dart b/lib/modules/tautulli/modules/logs_tautulli/route.dart index e3b7dfaf..dff2d0e8 100644 --- a/lib/modules/tautulli/modules/logs_tautulli/route.dart +++ b/lib/modules/tautulli/modules/logs_tautulli/route.dart @@ -8,29 +8,17 @@ import 'package:tautulli/tautulli.dart'; class TautulliLogsTautulliRouter { static const String ROUTE_NAME = '/tautulli/logs/tautulli'; - static Future navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( context, route(), ); - static String route({ String profile }) => [ - ROUTE_NAME, - if(profile != null) '/$profile', - ].join(); + static String route() => ROUTE_NAME; static void defineRoutes(Router router) { router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => _TautulliLogsTautulliRoute( - profile: null, - )), - transitionType: LunaRouter.transitionType, - ); - router.define( - ROUTE_NAME + '/:profile', - handler: Handler(handlerFunc: (context, params) => _TautulliLogsTautulliRoute( - profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null, - )), + handler: Handler(handlerFunc: (context, params) => _TautulliLogsTautulliRoute()), transitionType: LunaRouter.transitionType, ); } @@ -39,13 +27,6 @@ class TautulliLogsTautulliRouter { } class _TautulliLogsTautulliRoute extends StatefulWidget { - final String profile; - - _TautulliLogsTautulliRoute({ - Key key, - @required this.profile, - }) : super(key: key); - @override State createState() => _State(); } @@ -55,9 +36,8 @@ class _State extends State<_TautulliLogsTautulliRoute> { final GlobalKey _refreshKey = GlobalKey(); Future _refresh() async { - TautulliLocalState _state = Provider.of(context, listen: false); - _state.resetTautulliLogs(context); - await _state.tautulliLogs; + context.read().resetTautulliLogs(); + await context.read().tautulliLogs; } @override @@ -73,19 +53,23 @@ class _State extends State<_TautulliLogsTautulliRoute> { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Tautulli Logs'); + Widget get _appBar => LunaAppBar( + context: context, + title: 'Tautulli Logs', + popUntil: '/tautulli', + ); Widget get _body => LSRefreshIndicator( onRefresh: _refresh, refreshKey: _refreshKey, - child: Selector>>( + child: Selector>>( selector: (_, state) => state.tautulliLogs, builder: (context, logs, _) => FutureBuilder( future: logs, builder: (context, AsyncSnapshot> snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( '_TautulliLogsTautulliRoute', '_body', 'Unable to fetch Tautulli Tautulli logs', diff --git a/lib/modules/tautulli/modules/logs_tautulli/widgets/log_tile.dart b/lib/modules/tautulli/modules/logs_tautulli/widgets/log_tile.dart index 9da9903a..ae2db5e9 100644 --- a/lib/modules/tautulli/modules/logs_tautulli/widgets/log_tile.dart +++ b/lib/modules/tautulli/modules/logs_tautulli/widgets/log_tile.dart @@ -37,7 +37,7 @@ class TautulliLogsTautulliLogTile extends StatelessWidget { TextSpan( text: log.timestamp, style: TextStyle( - color: LSColors.accent, + color: LunaColours.accent, fontWeight: FontWeight.w600, ) ), @@ -65,11 +65,11 @@ class TautulliLogsTautulliLogTile extends StatelessWidget { children: [ LSTextHighlighted( text: log.level, - bgColor: LSColors.blue, + bgColor: LunaColours.blue, ), LSTextHighlighted( text: log.timestamp, - bgColor: LSColors.accent, + bgColor: LunaColours.accent, ), ], ), diff --git a/lib/modules/tautulli/modules/media_details/route.dart b/lib/modules/tautulli/modules/media_details/route.dart index 2cd6b184..b953de2f 100644 --- a/lib/modules/tautulli/modules/media_details/route.dart +++ b/lib/modules/tautulli/modules/media_details/route.dart @@ -10,7 +10,7 @@ class TautulliMediaDetailsRouter { static Future navigateTo(BuildContext context, { @required int ratingKey, @required TautulliMediaType mediaType, - }) async => TautulliRouter.router.navigateTo( + }) async => LunaRouter.router.navigateTo( context, route(ratingKey: ratingKey, mediaType: mediaType), ); @@ -18,34 +18,14 @@ class TautulliMediaDetailsRouter { static String route({ @required int ratingKey, @required TautulliMediaType mediaType, - String profile, - }) => [ - ROUTE_NAME - .replaceFirst(':mediatype', mediaType?.value ?? 'mediatype') - .replaceFirst(':ratingkey', ratingKey.toString()), - if(profile != null) '/$profile', - ].join(); - + }) => ROUTE_NAME + .replaceFirst(':mediatype', mediaType?.value ?? 'mediatype') + .replaceFirst(':ratingkey', ratingKey.toString()); + static void defineRoutes(Router router) { router.define( ROUTE_NAME, handler: Handler(handlerFunc: (context, params) => _TautulliMediaDetailsRoute( - profile: null, - ratingKey: params['ratingkey'] != null && params['ratingkey'].length != 0 - ? int.tryParse(params['ratingkey'][0]) - : null, - mediaType: params['mediatype'] != null && params['mediatype'].length != 0 - ? TautulliMediaType.NULL.from(params['mediatype'][0]) - : null, - )), - transitionType: LunaRouter.transitionType, - ); - router.define( - ROUTE_NAME + '/:profile', - handler: Handler(handlerFunc: (context, params) => _TautulliMediaDetailsRoute( - profile: params['profile'] != null && params['profile'].length != 0 - ? params['profile'][0] - : null, ratingKey: params['ratingkey'] != null && params['ratingkey'].length != 0 ? int.tryParse(params['ratingkey'][0]) : null, @@ -61,13 +41,11 @@ class TautulliMediaDetailsRouter { } class _TautulliMediaDetailsRoute extends StatefulWidget { - final String profile; final int ratingKey; final TautulliMediaType mediaType; _TautulliMediaDetailsRoute({ Key key, - @required this.profile, @required this.ratingKey, @required this.mediaType, }) : super(key: key); @@ -96,7 +74,11 @@ class _State extends State<_TautulliMediaDetailsRoute> { : _contentNotFound, ); - Widget get _appBar => LSAppBar(title: 'Media Details'); + Widget get _appBar => LunaAppBar( + context: context, + title: 'Media Details', + popUntil: '/tautulli', + ); Widget get _bottomNavigationBar { if( diff --git a/lib/modules/tautulli/modules/media_details/widgets/history.dart b/lib/modules/tautulli/modules/media_details/widgets/history.dart index 18372f10..264c4d10 100644 --- a/lib/modules/tautulli/modules/media_details/widgets/history.dart +++ b/lib/modules/tautulli/modules/media_details/widgets/history.dart @@ -32,18 +32,16 @@ class _State extends State with AutomaticKeepAliveC } Future _refresh() async { - TautulliState _global = Provider.of(context, listen: false); - TautulliLocalState _local = Provider.of(context, listen: false); - _local.setHistory( + context.read().setIndividualHistory( widget.ratingKey, - _global.api.history.getHistory( + context.read().api.history.getHistory( ratingKey: _ratingKey, parentRatingKey: _parentRatingKey, grandparentRatingKey: _grandparentRatingKey, length: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, ), ); - await _local.history[widget.ratingKey]; + await context.read().individualHistory[widget.ratingKey]; } int get _ratingKey { @@ -85,11 +83,11 @@ class _State extends State with AutomaticKeepAliveC refreshKey: _refreshKey, onRefresh: _refresh, child: FutureBuilder( - future: Provider.of(context).history[widget.ratingKey], + future: context.watch().individualHistory[widget.ratingKey], builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( 'TautulliMediaDetailsHistory', '_body', 'Unable to fetch Tautulli history: ${widget.ratingKey}', diff --git a/lib/modules/tautulli/modules/media_details/widgets/metadata.dart b/lib/modules/tautulli/modules/media_details/widgets/metadata.dart index 33ee91d0..3d9727e1 100644 --- a/lib/modules/tautulli/modules/media_details/widgets/metadata.dart +++ b/lib/modules/tautulli/modules/media_details/widgets/metadata.dart @@ -32,13 +32,11 @@ class _State extends State with AutomaticKeepAlive } Future _refresh() async { - TautulliState _global = Provider.of(context, listen: false); - TautulliLocalState _local = Provider.of(context, listen: false); - _local.setMetadata( + context.read().setMetadata( widget.ratingKey, - _global.api.libraries.getMetadata(ratingKey: widget.ratingKey), + context.read().api.libraries.getMetadata(ratingKey: widget.ratingKey), ); - await _local.metadata[widget.ratingKey]; + await context.read().metadata[widget.ratingKey]; } @override @@ -54,11 +52,11 @@ class _State extends State with AutomaticKeepAlive refreshKey: _refreshKey, onRefresh: _refresh, child: FutureBuilder( - future: Provider.of(context).metadata[widget.ratingKey], + future: context.watch().metadata[widget.ratingKey], builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( 'TautulliMediaDetailsMetadata', '_body', 'Unable to fetch Tautulli metadata: ${widget.ratingKey}', diff --git a/lib/modules/tautulli/modules/media_details/widgets/metadata_header.dart b/lib/modules/tautulli/modules/media_details/widgets/metadata_header.dart index d142913d..34225396 100644 --- a/lib/modules/tautulli/modules/media_details/widgets/metadata_header.dart +++ b/lib/modules/tautulli/modules/media_details/widgets/metadata_header.dart @@ -19,12 +19,11 @@ class TautulliMediaDetailsMetadataHeaderTile extends StatelessWidget { child: _body(context), decoration: metadata.art != null && metadata.art.isNotEmpty ? LSCardBackground( - uri: Provider.of(context, listen: false).getImageURLFromPath( + uri: context.watch().getImageURLFromPath( metadata.art, width: MediaQuery.of(context).size.width.truncate(), ), - headers: Provider.of(context, listen: false).headers, - darken: true, + headers: context.watch().headers, ) : null, ); @@ -56,11 +55,11 @@ class TautulliMediaDetailsMetadataHeaderTile extends StatelessWidget { Widget _poster(BuildContext context) { return LSNetworkImage( - url: Provider.of(context, listen: false).getImageURLFromPath(_posterLink), + url: context.watch().getImageURLFromPath(_posterLink), placeholder: 'assets/images/sonarr/noseriesposter.png', height: _height, width: _width, - headers: Provider.of(context, listen: false).headers.cast(), + headers: context.watch().headers.cast(), ); } diff --git a/lib/modules/tautulli/modules/media_details/widgets/metadata_summary.dart b/lib/modules/tautulli/modules/media_details/widgets/metadata_summary.dart index 03cc71af..da5a447f 100644 --- a/lib/modules/tautulli/modules/media_details/widgets/metadata_summary.dart +++ b/lib/modules/tautulli/modules/media_details/widgets/metadata_summary.dart @@ -27,7 +27,7 @@ class TautulliMediaDetailsMetadataSummary extends StatelessWidget { padding: EdgeInsets.all(12.0), ), borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), - onTap: () async => GlobalDialogs.textPreview(context, metadata.title, metadata.summary.trim()), + onTap: () async => LunaDialogs.textPreview(context, metadata.title, metadata.summary.trim()), ), ); } diff --git a/lib/modules/tautulli/modules/media_details/widgets/switcher_buttons.dart b/lib/modules/tautulli/modules/media_details/widgets/switcher_buttons.dart index 2791165f..4f271d4d 100644 --- a/lib/modules/tautulli/modules/media_details/widgets/switcher_buttons.dart +++ b/lib/modules/tautulli/modules/media_details/widgets/switcher_buttons.dart @@ -42,28 +42,29 @@ class _State extends State { List _buttonBuilder() { switch(widget.type) { case TautulliMediaType.SEASON: return [ - _button(TautulliMediaDetailsSwitcherType.GO_TO_SERIES, LSColors.accent), + _button(TautulliMediaDetailsSwitcherType.GO_TO_SERIES, LunaColours.accent), ]; case TautulliMediaType.EPISODE: return [ - _button(TautulliMediaDetailsSwitcherType.GO_TO_SERIES, LSColors.accent), - _button(TautulliMediaDetailsSwitcherType.GO_TO_SEASON, LSColors.orange), + _button(TautulliMediaDetailsSwitcherType.GO_TO_SERIES, LunaColours.accent), + _button(TautulliMediaDetailsSwitcherType.GO_TO_SEASON, LunaColours.orange), ]; case TautulliMediaType.ALBUM: return [ - _button(TautulliMediaDetailsSwitcherType.GO_TO_ARTIST, LSColors.accent), + _button(TautulliMediaDetailsSwitcherType.GO_TO_ARTIST, LunaColours.accent), ]; case TautulliMediaType.TRACK: return [ - _button(TautulliMediaDetailsSwitcherType.GO_TO_ARTIST, LSColors.accent), - _button(TautulliMediaDetailsSwitcherType.GO_TO_ALBUM, LSColors.orange), + _button(TautulliMediaDetailsSwitcherType.GO_TO_ARTIST, LunaColours.accent), + _button(TautulliMediaDetailsSwitcherType.GO_TO_ALBUM, LunaColours.orange), ]; default: return []; } } Widget _button(TautulliMediaDetailsSwitcherType type, Color color) => Expanded( - child: LSButtonSlim( + child: LSButton( text: type.label, onTap: () => _onTap(type), backgroundColor: color, + reducedMargin: true, ), ); diff --git a/lib/modules/tautulli/modules/more/widgets/check_for_updates_tile.dart b/lib/modules/tautulli/modules/more/widgets/check_for_updates_tile.dart index 26b8cab0..33de3219 100644 --- a/lib/modules/tautulli/modules/more/widgets/check_for_updates_tile.dart +++ b/lib/modules/tautulli/modules/more/widgets/check_for_updates_tile.dart @@ -9,7 +9,7 @@ class TautulliMoreCheckForUpdatesTile extends StatelessWidget { subtitle: LSSubtitle(text: 'Tautulli & Plex Updates'), trailing: LSIconButton( icon: Icons.system_update, - color: LSColors.list(0), + color: LunaColours.list(0), ), onTap: () async => _onTap(context), ); diff --git a/lib/modules/tautulli/modules/more/widgets/graphs_tile.dart b/lib/modules/tautulli/modules/more/widgets/graphs_tile.dart index 97c728cb..af2050b9 100644 --- a/lib/modules/tautulli/modules/more/widgets/graphs_tile.dart +++ b/lib/modules/tautulli/modules/more/widgets/graphs_tile.dart @@ -9,7 +9,7 @@ class TautulliMoreGraphsTile extends StatelessWidget { subtitle: LSSubtitle(text: 'Play Count & Duration Graphs'), trailing: LSIconButton( icon: Icons.insert_chart, - color: LSColors.list(1), + color: LunaColours.list(1), ), onTap: () async => _onTap(context), ); diff --git a/lib/modules/tautulli/modules/more/widgets/libraries_tile.dart b/lib/modules/tautulli/modules/more/widgets/libraries_tile.dart index 4d2af64a..eda8f319 100644 --- a/lib/modules/tautulli/modules/more/widgets/libraries_tile.dart +++ b/lib/modules/tautulli/modules/more/widgets/libraries_tile.dart @@ -9,7 +9,7 @@ class TautulliMoreLibrariesTile extends StatelessWidget { subtitle: LSSubtitle(text: 'Plex Library Information'), trailing: LSIconButton( icon: Icons.video_library, - color: LSColors.list(2), + color: LunaColours.list(2), ), onTap: () async => _onTap(context), ); diff --git a/lib/modules/tautulli/modules/more/widgets/logs_tile.dart b/lib/modules/tautulli/modules/more/widgets/logs_tile.dart index 1cbb8d07..fcf8a1ea 100644 --- a/lib/modules/tautulli/modules/more/widgets/logs_tile.dart +++ b/lib/modules/tautulli/modules/more/widgets/logs_tile.dart @@ -9,7 +9,7 @@ class TautulliMoreLogsTile extends StatelessWidget { subtitle: LSSubtitle(text: 'Tautulli & Plex Logs'), trailing: LSIconButton( icon: Icons.developer_mode, - color: LSColors.list(3), + color: LunaColours.list(3), ), onTap: () async => _onTap(context), ); diff --git a/lib/modules/tautulli/modules/more/widgets/recently_added_tile.dart b/lib/modules/tautulli/modules/more/widgets/recently_added_tile.dart index 87002db2..46a286ab 100644 --- a/lib/modules/tautulli/modules/more/widgets/recently_added_tile.dart +++ b/lib/modules/tautulli/modules/more/widgets/recently_added_tile.dart @@ -9,7 +9,7 @@ class TautulliMoreRecentlyAddedTile extends StatelessWidget { subtitle: LSSubtitle(text: 'Recently Added Content to Plex'), trailing: LSIconButton( icon: Icons.recent_actors, - color: LSColors.list(4), + color: LunaColours.list(4), ), onTap: () async => _onTap(context), ); diff --git a/lib/modules/tautulli/modules/more/widgets/search_tile.dart b/lib/modules/tautulli/modules/more/widgets/search_tile.dart index 277aaffd..c37e7097 100644 --- a/lib/modules/tautulli/modules/more/widgets/search_tile.dart +++ b/lib/modules/tautulli/modules/more/widgets/search_tile.dart @@ -9,7 +9,7 @@ class TautulliMoreSearchTile extends StatelessWidget { subtitle: LSSubtitle(text: 'Search Your Libraries'), trailing: LSIconButton( icon: Icons.search, - color: LSColors.list(5), + color: LunaColours.list(5), ), onTap: () async => _onTap(context), ); diff --git a/lib/modules/tautulli/modules/more/widgets/statistics_tile.dart b/lib/modules/tautulli/modules/more/widgets/statistics_tile.dart index 82bfa70e..3a456421 100644 --- a/lib/modules/tautulli/modules/more/widgets/statistics_tile.dart +++ b/lib/modules/tautulli/modules/more/widgets/statistics_tile.dart @@ -9,7 +9,7 @@ class TautulliMoreStatisticsTile extends StatelessWidget { subtitle: LSSubtitle(text: 'User & Library Statistics'), trailing: LSIconButton( icon: Icons.format_list_numbered, - color: LSColors.list(6), + color: LunaColours.list(6), ), onTap: () async => _onTap(context), ); diff --git a/lib/modules/tautulli/modules/more/widgets/synced_items_tile.dart b/lib/modules/tautulli/modules/more/widgets/synced_items_tile.dart index b08945d8..69d45b66 100644 --- a/lib/modules/tautulli/modules/more/widgets/synced_items_tile.dart +++ b/lib/modules/tautulli/modules/more/widgets/synced_items_tile.dart @@ -9,7 +9,7 @@ class TautulliMoreSyncedItemsTile extends StatelessWidget { subtitle: LSSubtitle(text: 'Synced Content on Devices'), trailing: LSIconButton( icon: Icons.sync, - color: LSColors.list(7), + color: LunaColours.list(7), ), onTap: () async => _onTap(context), ); diff --git a/lib/modules/tautulli/modules/recently_added/route.dart b/lib/modules/tautulli/modules/recently_added/route.dart index b3cbb9ba..b9ea56e3 100644 --- a/lib/modules/tautulli/modules/recently_added/route.dart +++ b/lib/modules/tautulli/modules/recently_added/route.dart @@ -8,31 +8,17 @@ import 'package:tautulli/tautulli.dart'; class TautulliRecentlyAddedRouter { static const String ROUTE_NAME = '/tautulli/recentlyadded/list'; - static Future navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( context, route(), ); - static String route({ - String profile, - }) => [ - ROUTE_NAME, - if(profile != null) '/$profile', - ].join(); + static String route() => ROUTE_NAME; static void defineRoutes(Router router) { router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => _TautulliRecentlyAddedRoute(profile: null)), - transitionType: LunaRouter.transitionType, - ); - router.define( - ROUTE_NAME + '/:profile', - handler: Handler(handlerFunc: (context, params) => _TautulliRecentlyAddedRoute( - profile: params['profile'] != null && params['profile'].length != 0 - ? params['profile'][0] - : null, - )), + handler: Handler(handlerFunc: (context, params) => _TautulliRecentlyAddedRoute()), transitionType: LunaRouter.transitionType, ); } @@ -41,13 +27,6 @@ class TautulliRecentlyAddedRouter { } class _TautulliRecentlyAddedRoute extends StatefulWidget { - final String profile; - - _TautulliRecentlyAddedRoute({ - Key key, - @required this.profile, - }) : super(key: key); - @override State<_TautulliRecentlyAddedRoute> createState() => _State(); } @@ -57,9 +36,8 @@ class _State extends State<_TautulliRecentlyAddedRoute> { final GlobalKey _refreshKey = GlobalKey(); Future _refresh() async { - TautulliLocalState _state = Provider.of(context, listen: false); - _state.resetRecentlyAdded(context); - await _state.recentlyAdded; + context.read().resetRecentlyAdded(); + await context.read().recentlyAdded; } @override @@ -75,19 +53,23 @@ class _State extends State<_TautulliRecentlyAddedRoute> { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Recently Added'); + Widget get _appBar => LunaAppBar( + context: context, + title: 'Recently Added', + popUntil: '/tautulli', + ); Widget get _body => LSRefreshIndicator( refreshKey: _refreshKey, onRefresh: _refresh, - child: Selector>>( + child: Selector>>( selector: (_, state) => state.recentlyAdded, builder: (context, stats, _) => FutureBuilder( future: stats, builder: (context, AsyncSnapshot> snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( '_TautulliRecentlyAddedRoute', '_body', 'Unable to fetch Tautulli recently added', diff --git a/lib/modules/tautulli/modules/recently_added/widgets/content_tile.dart b/lib/modules/tautulli/modules/recently_added/widgets/content_tile.dart index 9565daa7..fabd3d97 100644 --- a/lib/modules/tautulli/modules/recently_added/widgets/content_tile.dart +++ b/lib/modules/tautulli/modules/recently_added/widgets/content_tile.dart @@ -27,12 +27,11 @@ class TautulliRecentlyAddedContentTile extends StatelessWidget { ), decoration: recentlyAdded.art != null && recentlyAdded.art.isNotEmpty ? LSCardBackground( - darken: true, - uri: Provider.of(context, listen: false).getImageURLFromPath( + uri: context.watch().getImageURLFromPath( recentlyAdded.art, width: MediaQuery.of(context).size.width.truncate(), ), - headers: Provider.of(context, listen: false).headers.cast(), + headers: context.watch().headers.cast(), ) : null, ); @@ -55,8 +54,8 @@ class TautulliRecentlyAddedContentTile extends StatelessWidget { Widget _poster(BuildContext context) { return LSNetworkImage( - url: Provider.of(context, listen: false).getImageURLFromPath(_posterLink), - headers: Provider.of(context, listen: false).headers.cast(), + url: context.watch().getImageURLFromPath(_posterLink), + headers: context.watch().headers.cast(), placeholder: 'assets/images/sonarr/noseriesposter.png', height: _imageDimension, width: _imageDimension/1.5, diff --git a/lib/modules/tautulli/modules/search/route.dart b/lib/modules/tautulli/modules/search/route.dart index eea3314e..0f84f40c 100644 --- a/lib/modules/tautulli/modules/search/route.dart +++ b/lib/modules/tautulli/modules/search/route.dart @@ -8,31 +8,17 @@ class TautulliSearchRouter { static Future navigateTo({ @required BuildContext context, - }) async => TautulliRouter.router.navigateTo( + }) async => LunaRouter.router.navigateTo( context, route(), ); - static String route({ - String profile, - }) => [ - ROUTE_NAME, - if(profile != null) '/$profile', - ].join(); + static String route() => ROUTE_NAME; static void defineRoutes(Router router) { router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => _TautulliSearchRoute(profile: null)), - transitionType: LunaRouter.transitionType, - ); - router.define( - ROUTE_NAME + '/:profile', - handler: Handler(handlerFunc: (context, params) => _TautulliSearchRoute( - profile: params['profile'] != null && params['profile'].length != 0 - ? params['profile'][0] - : null, - )), + handler: Handler(handlerFunc: (context, params) => _TautulliSearchRoute()), transitionType: LunaRouter.transitionType, ); } @@ -41,13 +27,6 @@ class TautulliSearchRouter { } class _TautulliSearchRoute extends StatefulWidget { - final String profile; - - _TautulliSearchRoute({ - @required this.profile, - Key key, - }) : super(key: key); - @override State<_TautulliSearchRoute> createState() => _State(); } @@ -58,7 +37,7 @@ class _State extends State<_TautulliSearchRoute> { @override Widget build(BuildContext context) => Scaffold( key: _scaffoldKey, - appBar: TautulliSearchAppBar(), + appBar: TautulliSearchAppBar(context: context), body: TautulliSearchSearchResults(), ); } \ No newline at end of file diff --git a/lib/modules/tautulli/modules/search/widgets/search_appbar.dart b/lib/modules/tautulli/modules/search/widgets/search_appbar.dart index e47620a6..3bff3ffa 100644 --- a/lib/modules/tautulli/modules/search/widgets/search_appbar.dart +++ b/lib/modules/tautulli/modules/search/widgets/search_appbar.dart @@ -3,22 +3,18 @@ import 'package:lunasea/core.dart'; import 'package:lunasea/modules/tautulli.dart'; // ignore: non_constant_identifier_names -Widget TautulliSearchAppBar() => AppBar( - title: Text( - 'Search', - overflow: TextOverflow.fade, - style: TextStyle( - fontSize: Constants.UI_FONT_SIZE_HEADER, - ), - ), - centerTitle: false, - elevation: 0, +Widget TautulliSearchAppBar({ + @required BuildContext context, +}) => LunaAppBar( + context: context, + title: 'Search', bottom: _SearchBar(), + popUntil: '/tautulli', ); class _SearchBar extends StatefulWidget implements PreferredSizeWidget { @override - Size get preferredSize => Size.fromHeight(60.0); + Size get preferredSize => Size.fromHeight(62.0); @override State<_SearchBar> createState() => _State(); @@ -30,30 +26,34 @@ class _State extends State<_SearchBar> { @override void initState() { super.initState(); - _controller.text = Provider.of(context, listen: false).searchQuery; + _controller.text = context.read().searchQuery; } @override Widget build(BuildContext context) => Container( - child: Expanded( - child: Consumer( - builder: (context, state, widget) => LSTextInputBar( - controller: _controller, - autofocus: state.searchQuery.isEmpty, - onChanged: (text, updateController) => _onChange(state, text, updateController), - onSubmitted: _onSubmit, - margin: EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 12.0), - ), + child: Consumer( + builder: (context, state, widget) => Row( + children: [ + Expanded( + child: LSTextInputBar( + controller: _controller, + autofocus: state.searchQuery.isEmpty, + onChanged: (text, updateController) => _onChange(text, updateController), + onSubmitted: _onSubmit, + margin: EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 14.0), + ), + ), + ], ), ), ); - void _onChange(TautulliLocalState state, String text, bool updateController) { - state.searchQuery = text; + void _onChange(String text, bool updateController) { + context.read().searchQuery = text; if(updateController) _controller.text = text; } Future _onSubmit(String value) async { - if(value.isNotEmpty) Provider.of(context, listen: false).fetchSearch(context); + if(value.isNotEmpty) context.read().fetchSearch(); } } diff --git a/lib/modules/tautulli/modules/search/widgets/search_result_tile.dart b/lib/modules/tautulli/modules/search/widgets/search_result_tile.dart index f70f582f..55326cc8 100644 --- a/lib/modules/tautulli/modules/search/widgets/search_result_tile.dart +++ b/lib/modules/tautulli/modules/search/widgets/search_result_tile.dart @@ -28,16 +28,15 @@ class TautulliSearchResultTile extends StatelessWidget { ), decoration: result.art != null && result.art.isNotEmpty ? LSCardBackground( - uri: Provider.of(context, listen: false).getImageURLFromPath(result.art), - headers: Provider.of(context, listen: false).headers, - darken: true, + uri: context.watch().getImageURLFromPath(result.art), + headers: context.watch().headers, ) : null, ); Widget _poster(BuildContext context) => LSNetworkImage( - url: Provider.of(context, listen: false).getImageURLFromPath(result.thumb), - headers: Provider.of(context, listen: false).headers.cast(), + url: context.watch().getImageURLFromPath(result.thumb), + headers: context.watch().headers.cast(), height: _imageDimension, width: _imageDimension/1.5, placeholder: 'assets/images/sonarr/noseriesposter.png', @@ -91,7 +90,7 @@ class TautulliSearchResultTile extends StatelessWidget { overflow: TextOverflow.fade, softWrap: false, style: TextStyle( - color: LSColors.accent, + color: LunaColours.accent, fontWeight: FontWeight.w600, ), ); diff --git a/lib/modules/tautulli/modules/search/widgets/search_results.dart b/lib/modules/tautulli/modules/search/widgets/search_results.dart index 859c8b6e..5600afb8 100644 --- a/lib/modules/tautulli/modules/search/widgets/search_results.dart +++ b/lib/modules/tautulli/modules/search/widgets/search_results.dart @@ -11,10 +11,10 @@ class TautulliSearchSearchResults extends StatefulWidget { class _State extends State { final GlobalKey _refreshKey = GlobalKey(); - Future _refresh() async => Provider.of(context, listen: false).fetchSearch(context); + Future _refresh() async => context.read().fetchSearch(); @override - Widget build(BuildContext context) => Selector>( + Widget build(BuildContext context) => Selector>( selector: (_, state) => state.search, builder: (context, future, _) { if(future == null) return Container(); @@ -30,7 +30,7 @@ class _State extends State { builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( 'TautulliSearchSearchResults', '_body', 'Unable to fetch Tautulli search results', diff --git a/lib/modules/tautulli/modules/statistics/route.dart b/lib/modules/tautulli/modules/statistics/route.dart index b51bf648..09e6a359 100644 --- a/lib/modules/tautulli/modules/statistics/route.dart +++ b/lib/modules/tautulli/modules/statistics/route.dart @@ -8,29 +8,17 @@ import 'package:tautulli/tautulli.dart'; class TautulliStatisticsRouter { static const String ROUTE_NAME = '/tautulli/statistics/list'; - static Future navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( context, route(), ); - static String route({ String profile }) => [ - ROUTE_NAME, - if(profile != null) '/$profile', - ].join(); + static String route() => ROUTE_NAME; static void defineRoutes(Router router) { - router.define( - ROUTE_NAME + '/:profile', - handler: Handler(handlerFunc: (context, params) => _TautulliStatisticsRoute( - profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null, - )), - transitionType: LunaRouter.transitionType, - ); router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => _TautulliStatisticsRoute( - profile: null, - )), + handler: Handler(handlerFunc: (context, params) => _TautulliStatisticsRoute()), transitionType: LunaRouter.transitionType, ); } @@ -39,13 +27,6 @@ class TautulliStatisticsRouter { } class _TautulliStatisticsRoute extends StatefulWidget { - final String profile; - - _TautulliStatisticsRoute({ - Key key, - @required this.profile, - }) : super(key: key); - @override State<_TautulliStatisticsRoute> createState() => _State(); } @@ -55,9 +36,8 @@ class _State extends State<_TautulliStatisticsRoute> { final GlobalKey _refreshKey = GlobalKey(); Future _refresh() async { - TautulliLocalState _state = Provider.of(context, listen: false); - _state.resetStatistics(context); - await _state.statistics; + context.read().resetStatistics(); + await context.read().statistics; } @override @@ -73,8 +53,10 @@ class _State extends State<_TautulliStatisticsRoute> { body: _body, ); - Widget get _appBar => LSAppBar( + Widget get _appBar => LunaAppBar( + context: context, title: 'Statistics', + popUntil: '/tautulli', actions: [ TautulliStatisticsTypeButton(), TautulliStatisticsTimeRangeButton(), @@ -84,14 +66,14 @@ class _State extends State<_TautulliStatisticsRoute> { Widget get _body => LSRefreshIndicator( refreshKey: _refreshKey, onRefresh: _refresh, - child: Selector>>( + child: Selector>>( selector: (_, state) => state.statistics, builder: (context, stats, _) => FutureBuilder( future: stats, builder: (context, AsyncSnapshot> snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( '_TautulliStatisticsRoute', '_body', 'Unable to fetch Tautulli statistics', diff --git a/lib/modules/tautulli/modules/statistics/widgets/media_tile.dart b/lib/modules/tautulli/modules/statistics/widgets/media_tile.dart index e059e58d..001b27ce 100644 --- a/lib/modules/tautulli/modules/statistics/widgets/media_tile.dart +++ b/lib/modules/tautulli/modules/statistics/widgets/media_tile.dart @@ -29,19 +29,18 @@ class TautulliStatisticsMediaTile extends StatelessWidget { ), decoration: data['art'] != null && (data['art'] as String).isNotEmpty ? LSCardBackground( - darken: true, - uri: Provider.of(context, listen: false).getImageURLFromPath( + uri: context.watch().getImageURLFromPath( data['art'], width: MediaQuery.of(context).size.width.truncate(), ), - headers: Provider.of(context, listen: false).headers.cast(), + headers: context.watch().headers.cast(), ) : null, ); Widget _poster(BuildContext context) => LSNetworkImage( - url: Provider.of(context, listen: false).getImageURLFromPath(data['thumb']), - headers: Provider.of(context, listen: false).headers.cast(), + url: context.watch().getImageURLFromPath(data['thumb']), + headers: context.watch().headers.cast(), placeholder: 'assets/images/sonarr/noseriesposter.png', height: _imageDimension, width: _imageDimension/1.5, @@ -80,10 +79,10 @@ class TautulliStatisticsMediaTile extends StatelessWidget { TextSpan( text: data['total_plays'].toString() + (data['total_plays'] == 1 ? ' Play' : ' Plays'), style: TextStyle( - color: Provider.of(context, listen: false).statisticsType == TautulliStatsType.PLAYS - ? LSColors.accent + color: context.watch().statisticsType == TautulliStatsType.PLAYS + ? LunaColours.accent : null, - fontWeight: Provider.of(context, listen: false).statisticsType == TautulliStatsType.PLAYS + fontWeight: context.watch().statisticsType == TautulliStatsType.PLAYS ? FontWeight.w600 : null, ), @@ -93,10 +92,10 @@ class TautulliStatisticsMediaTile extends StatelessWidget { ? TextSpan( text: Duration(seconds: data['total_duration']).lsDuration_fullTimestamp(), style: TextStyle( - color: Provider.of(context, listen: false).statisticsType == TautulliStatsType.DURATION - ? LSColors.accent + color: context.watch().statisticsType == TautulliStatsType.DURATION + ? LunaColours.accent : null, - fontWeight: Provider.of(context, listen: false).statisticsType == TautulliStatsType.DURATION + fontWeight: context.watch().statisticsType == TautulliStatsType.DURATION ? FontWeight.w600 : null, ), diff --git a/lib/modules/tautulli/modules/statistics/widgets/platform_tile.dart b/lib/modules/tautulli/modules/statistics/widgets/platform_tile.dart index d0afea59..98e9f588 100644 --- a/lib/modules/tautulli/modules/statistics/widgets/platform_tile.dart +++ b/lib/modules/tautulli/modules/statistics/widgets/platform_tile.dart @@ -68,10 +68,10 @@ class TautulliStatisticsPlatformTile extends StatelessWidget { TextSpan( text: data['total_plays'].toString() + (data['total_plays'] == 1 ? ' Play' : ' Plays'), style: TextStyle( - color: Provider.of(context, listen: false).statisticsType == TautulliStatsType.PLAYS - ? LSColors.accent + color: context.watch().statisticsType == TautulliStatsType.PLAYS + ? LunaColours.accent : null, - fontWeight: Provider.of(context, listen: false).statisticsType == TautulliStatsType.PLAYS + fontWeight: context.watch().statisticsType == TautulliStatsType.PLAYS ? FontWeight.w600 : null, ), @@ -81,10 +81,10 @@ class TautulliStatisticsPlatformTile extends StatelessWidget { ? TextSpan( text: Duration(seconds: data['total_duration']).lsDuration_fullTimestamp(), style: TextStyle( - color: Provider.of(context, listen: false).statisticsType == TautulliStatsType.DURATION - ? LSColors.accent + color: context.watch().statisticsType == TautulliStatsType.DURATION + ? LunaColours.accent : null, - fontWeight: Provider.of(context, listen: false).statisticsType == TautulliStatsType.DURATION + fontWeight: context.watch().statisticsType == TautulliStatsType.DURATION ? FontWeight.w600 : null, ), diff --git a/lib/modules/tautulli/modules/statistics/widgets/recently_watched_tile.dart b/lib/modules/tautulli/modules/statistics/widgets/recently_watched_tile.dart index 49b38b2a..6cebefec 100644 --- a/lib/modules/tautulli/modules/statistics/widgets/recently_watched_tile.dart +++ b/lib/modules/tautulli/modules/statistics/widgets/recently_watched_tile.dart @@ -27,19 +27,18 @@ class TautulliStatisticsRecentlyWatchedTile extends StatelessWidget { ), decoration: data['art'] != null && (data['art'] as String).isNotEmpty ? LSCardBackground( - darken: true, - uri: Provider.of(context, listen: false).getImageURLFromPath( + uri: context.watch().getImageURLFromPath( data['art'], width: MediaQuery.of(context).size.width.truncate(), ), - headers: Provider.of(context, listen: false).headers.cast(), + headers: context.watch().headers.cast(), ) : null, ); Widget _poster(BuildContext context) => LSNetworkImage( - url: Provider.of(context, listen: false).getImageURLFromPath(data['thumb']), - headers: Provider.of(context, listen: false).headers.cast(), + url: context.watch().getImageURLFromPath(data['thumb']), + headers: context.watch().headers.cast(), placeholder: 'assets/images/sonarr/noseriesposter.png', height: _imageDimension, width: _imageDimension/1.5, diff --git a/lib/modules/tautulli/modules/statistics/widgets/stream_tile.dart b/lib/modules/tautulli/modules/statistics/widgets/stream_tile.dart index 50ff8302..5c0fc8ce 100644 --- a/lib/modules/tautulli/modules/statistics/widgets/stream_tile.dart +++ b/lib/modules/tautulli/modules/statistics/widgets/stream_tile.dart @@ -67,7 +67,7 @@ class TautulliStatisticsStreamTile extends StatelessWidget { TextSpan( text: data['count'].toString() + (data['count'] == 1 ? ' Play' : ' Plays'), style: TextStyle( - color: LSColors.accent, + color: LunaColours.accent, fontWeight: FontWeight.w600, ), ), @@ -76,7 +76,7 @@ class TautulliStatisticsStreamTile extends StatelessWidget { ? TextSpan( text: LunaSeaDatabaseValue.USE_24_HOUR_TIME.data ? DateFormat('yyyy-MM-dd HH:mm').format(DateTime.fromMillisecondsSinceEpoch(int.tryParse(data['started']) * 1000)) - : DateFormat('yyyy-MM-dd KK:mm a').format(DateTime.fromMillisecondsSinceEpoch(int.tryParse(data['started']) * 1000)), + : DateFormat('yyyy-MM-dd hh:mm a').format(DateTime.fromMillisecondsSinceEpoch(int.tryParse(data['started']) * 1000)), ) : TextSpan(text: '${Constants.TEXT_EMDASH}'), TextSpan(text: '\n'), diff --git a/lib/modules/tautulli/modules/statistics/widgets/time_range_button.dart b/lib/modules/tautulli/modules/statistics/widgets/time_range_button.dart index 0ab342ad..2a535695 100644 --- a/lib/modules/tautulli/modules/statistics/widgets/time_range_button.dart +++ b/lib/modules/tautulli/modules/statistics/widgets/time_range_button.dart @@ -12,8 +12,8 @@ class TautulliStatisticsTimeRangeButton extends StatelessWidget { : LSRoundedShape(), icon: LSIcon(icon: Icons.access_time), onSelected: (value) { - Provider.of(context, listen: false).statisticsTimeRange = value; - Provider.of(context, listen: false).resetStatistics(context); + context.read().statisticsTimeRange = value; + context.read().resetStatistics(); }, itemBuilder: (context) => List>.generate( TautulliStatisticsTimeRange.values.length, @@ -24,7 +24,7 @@ class TautulliStatisticsTimeRangeButton extends StatelessWidget { style: TextStyle( fontSize: Constants.UI_FONT_SIZE_SUBTITLE, color: range == TautulliStatisticsTimeRange.values[index] - ? LSColors.accent + ? LunaColours.accent : Colors.white, ), ), diff --git a/lib/modules/tautulli/modules/statistics/widgets/type_button.dart b/lib/modules/tautulli/modules/statistics/widgets/type_button.dart index 8e1e16b7..0f989f42 100644 --- a/lib/modules/tautulli/modules/statistics/widgets/type_button.dart +++ b/lib/modules/tautulli/modules/statistics/widgets/type_button.dart @@ -13,8 +13,8 @@ class TautulliStatisticsTypeButton extends StatelessWidget { : LSRoundedShape(), icon: LSIcon(icon: Icons.merge_type), onSelected: (value) { - Provider.of(context, listen: false).statisticsType = value; - Provider.of(context, listen: false).resetStatistics(context); + context.read().statisticsType = value; + context.read().resetStatistics(); }, itemBuilder: (context) => List>.generate( TautulliStatsType.values.length, @@ -25,7 +25,7 @@ class TautulliStatisticsTypeButton extends StatelessWidget { style: TextStyle( fontSize: Constants.UI_FONT_SIZE_SUBTITLE, color: type == TautulliStatsType.values[index] - ? LSColors.accent + ? LunaColours.accent : Colors.white, ), ), diff --git a/lib/modules/tautulli/modules/statistics/widgets/user_tile.dart b/lib/modules/tautulli/modules/statistics/widgets/user_tile.dart index 7ce7008d..d6a41c9e 100644 --- a/lib/modules/tautulli/modules/statistics/widgets/user_tile.dart +++ b/lib/modules/tautulli/modules/statistics/widgets/user_tile.dart @@ -28,8 +28,8 @@ class TautulliStatisticsUserTile extends StatelessWidget { ); Widget _poster(BuildContext context) => LSNetworkImage( - url: Provider.of(context, listen: false).getImageURLFromPath(data['user_thumb']), - headers: Provider.of(context, listen: false).headers.cast(), + url: context.watch().getImageURLFromPath(data['user_thumb']), + headers: context.watch().headers.cast(), placeholder: 'assets/images/tautulli/nouserthumb.png', height: _imageDimension, width: _imageDimension/1.5, @@ -68,10 +68,10 @@ class TautulliStatisticsUserTile extends StatelessWidget { TextSpan( text: data['total_plays'].toString() + (data['total_plays'] == 1 ? ' Play' : ' Plays'), style: TextStyle( - color: Provider.of(context, listen: false).statisticsType == TautulliStatsType.PLAYS - ? LSColors.accent + color: context.watch().statisticsType == TautulliStatsType.PLAYS + ? LunaColours.accent : null, - fontWeight: Provider.of(context, listen: false).statisticsType == TautulliStatsType.PLAYS + fontWeight: context.watch().statisticsType == TautulliStatsType.PLAYS ? FontWeight.w600 : null, ), @@ -81,10 +81,10 @@ class TautulliStatisticsUserTile extends StatelessWidget { ? TextSpan( text: Duration(seconds: data['total_duration']).lsDuration_fullTimestamp(), style: TextStyle( - color: Provider.of(context, listen: false).statisticsType == TautulliStatsType.DURATION - ? LSColors.accent + color: context.watch().statisticsType == TautulliStatsType.DURATION + ? LunaColours.accent : null, - fontWeight: Provider.of(context, listen: false).statisticsType == TautulliStatsType.DURATION + fontWeight: context.watch().statisticsType == TautulliStatsType.DURATION ? FontWeight.w600 : null, ), @@ -105,7 +105,7 @@ class TautulliStatisticsUserTile extends StatelessWidget { ); Future _onTap(BuildContext context) async { - TautulliTableUser _user = await Provider.of(context, listen: false).users.then( + TautulliTableUser _user = await context.watch().users.then( (users) => users.users.firstWhere( (user) => user.userId == data['user_id'] ?? -1, orElse: null, diff --git a/lib/modules/tautulli/modules/synced_items/route.dart b/lib/modules/tautulli/modules/synced_items/route.dart index e288e5cf..7b0b6bf2 100644 --- a/lib/modules/tautulli/modules/synced_items/route.dart +++ b/lib/modules/tautulli/modules/synced_items/route.dart @@ -8,29 +8,17 @@ import 'package:tautulli/tautulli.dart'; class TautulliSyncedItemsRouter { static const String ROUTE_NAME = '/tautulli/synceditems/list'; - static Future navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( context, route(), ); - static String route({ String profile }) => [ - ROUTE_NAME, - if(profile != null) '/$profile', - ].join(); + static String route() => ROUTE_NAME; static void defineRoutes(Router router) { - router.define( - ROUTE_NAME + '/:profile', - handler: Handler(handlerFunc: (context, params) => _TautulliSyncedItemsRoute( - profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null, - )), - transitionType: LunaRouter.transitionType, - ); router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => _TautulliSyncedItemsRoute( - profile: null, - )), + handler: Handler(handlerFunc: (context, params) => _TautulliSyncedItemsRoute()), transitionType: LunaRouter.transitionType, ); } @@ -39,13 +27,6 @@ class TautulliSyncedItemsRouter { } class _TautulliSyncedItemsRoute extends StatefulWidget { - final String profile; - - _TautulliSyncedItemsRoute({ - Key key, - @required this.profile, - }): super(key: key); - @override State<_TautulliSyncedItemsRoute> createState() => _State(); } @@ -61,9 +42,8 @@ class _State extends State<_TautulliSyncedItemsRoute> { } Future _refresh() async { - TautulliLocalState _state = Provider.of(context, listen: false); - _state.resetSyncedItems(context); - await _state.syncedItems; + context.read().resetSyncedItems(); + await context.read().syncedItems; } @override @@ -73,19 +53,23 @@ class _State extends State<_TautulliSyncedItemsRoute> { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Synced Items'); + Widget get _appBar => LunaAppBar( + context: context, + title: 'Synced Items', + popUntil: '/tautulli', + ); Widget get _body => LSRefreshIndicator( refreshKey: _refreshKey, onRefresh: _refresh, - child: Selector>>( + child: Selector>>( selector: (_, state) => state.syncedItems, builder: (context, synced, _) => FutureBuilder( future: synced, builder: (context, AsyncSnapshot> snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( '_TautulliSyncedItemsRoute', '_body', 'Unable to fetch Tautulli synced items', diff --git a/lib/modules/tautulli/modules/synced_items/widgets/synced_item_tile.dart b/lib/modules/tautulli/modules/synced_items/widgets/synced_item_tile.dart index c4992a57..d20aaaf0 100644 --- a/lib/modules/tautulli/modules/synced_items/widgets/synced_item_tile.dart +++ b/lib/modules/tautulli/modules/synced_items/widgets/synced_item_tile.dart @@ -24,7 +24,7 @@ class TautulliSyncedItemTile extends StatelessWidget { TextSpan( text: (syncedItem.state ?? 'Unknown').lsLanguage_Capitalize(), style: TextStyle( - color: LSColors.accent, + color: LunaColours.accent, fontWeight: FontWeight.w600, ), ), @@ -48,12 +48,11 @@ class TautulliSyncedItemTile extends StatelessWidget { ), decoration: syncedItem.ratingKey != null ? LSCardBackground( - uri: Provider.of(context, listen: false).getImageURLFromRatingKey( + uri: context.watch().getImageURLFromRatingKey( syncedItem.ratingKey, width: MediaQuery.of(context).size.width.truncate(), ), - headers: Provider.of(context, listen: false).headers, - darken: true, + headers: context.watch().headers, ) : null, onTap: () async => _onTap(context), diff --git a/lib/modules/tautulli/modules/tautulli/route.dart b/lib/modules/tautulli/modules/tautulli/route.dart index b9d3590e..70dd3bfd 100644 --- a/lib/modules/tautulli/modules/tautulli/route.dart +++ b/lib/modules/tautulli/modules/tautulli/route.dart @@ -6,33 +6,17 @@ import 'package:lunasea/modules/tautulli.dart'; class TautulliHomeRouter { static const ROUTE_NAME = '/tautulli'; - static Future navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo( + static Future navigateTo(BuildContext context) async => LunaRouter.router.navigateTo( context, route(), ); - static String route({ String profile }) => [ - ROUTE_NAME, - if(profile != null) '/$profile', - ].join(); + static String route() => ROUTE_NAME; static void defineRoutes(Router router) { - /// With profile defined - router.define( - ROUTE_NAME + '/:profile', - handler: Handler(handlerFunc: (context, params) => _TautulliHomeRoute( - profile: params['profile'] != null && params['profile'].length != 0 - ? params['profile'][0] - : null, - )), - transitionType: LunaRouter.transitionType, - ); - /// Without profile defined router.define( ROUTE_NAME, - handler: Handler(handlerFunc: (context, params) => _TautulliHomeRoute( - profile: null, - )), + handler: Handler(handlerFunc: (context, params) => _TautulliHomeRoute()), transitionType: LunaRouter.transitionType, ); } @@ -41,18 +25,12 @@ class TautulliHomeRouter { } class _TautulliHomeRoute extends StatefulWidget { - final String profile; - - _TautulliHomeRoute({ - Key key, - @required this.profile, - }) : super(key: key); - @override State<_TautulliHomeRoute> createState() => _State(); } class _State extends State<_TautulliHomeRoute> { + final GlobalKey _scaffoldKey = GlobalKey(); PageController _pageController; @override @@ -62,17 +40,26 @@ class _State extends State<_TautulliHomeRoute> { } @override - Widget build(BuildContext context) => ValueListenableBuilder( - valueListenable: Database.lunaSeaBox.listenable(keys: [ LunaSeaDatabaseValue.ENABLED_PROFILE.key ]), - builder: (context, box, _) => Scaffold( - key: Provider.of(context, listen: false).rootScaffoldKey, - drawer: _drawer, - appBar: _appBar, - bottomNavigationBar: _bottomNavigationBar, - body: _body, + Widget build(BuildContext context) => WillPopScope( + onWillPop: _onWillPop, + child: ValueListenableBuilder( + valueListenable: Database.lunaSeaBox.listenable(keys: [ LunaSeaDatabaseValue.ENABLED_PROFILE.key ]), + builder: (context, box, _) => Scaffold( + key: _scaffoldKey, + drawer: _drawer, + appBar: _appBar, + bottomNavigationBar: _bottomNavigationBar, + body: _body, + ), ), ); + Future _onWillPop() async { + if(_scaffoldKey.currentState.isDrawerOpen) return true; + _scaffoldKey.currentState.openDrawer(); + return false; + } + Widget get _drawer => LSDrawer(page: 'tautulli'); Widget get _bottomNavigationBar => TautulliNavigationBar(pageController: _pageController); @@ -99,6 +86,6 @@ class _State extends State<_TautulliHomeRoute> { if((Database.profilesBox.get(element) as ProfileHiveObject)?.tautulliEnabled ?? false) value.add(element); return value; }), - actions: Provider.of(context).enabled ? [TautulliGlobalSettings()] : null, + actions: context.read().enabled ? [TautulliGlobalSettings()] : null, ); } diff --git a/lib/modules/tautulli/modules/tautulli/widgets/global_settings.dart b/lib/modules/tautulli/modules/tautulli/widgets/global_settings.dart index a82a8369..e2e8e5e6 100644 --- a/lib/modules/tautulli/modules/tautulli/widgets/global_settings.dart +++ b/lib/modules/tautulli/modules/tautulli/widgets/global_settings.dart @@ -18,21 +18,21 @@ class TautulliGlobalSettings extends StatelessWidget { case TautulliGlobalSettingsType.DELETE_CACHE: _deleteCache(context); break; case TautulliGlobalSettingsType.DELETE_IMAGE_CACHE: _deleteImageCache(context); break; case TautulliGlobalSettingsType.DELETE_TEMP_SESSIONS: _deleteTempSessions(context); break; - default: Logger.warning('TautulliGlobalSettings', '_handler', 'Unknown case: ${(values[1] as TautulliGlobalSettings)}'); + default: LunaLogger.warning('TautulliGlobalSettings', '_handler', 'Unknown case: ${(values[1] as TautulliGlobalSettings)}'); } } - Future _webGUI(BuildContext context) async => Provider.of(context, listen: false).host.lsLinks_OpenLink(); + Future _webGUI(BuildContext context) async => context.read().host.lsLinks_OpenLink(); Future _backupConfig(BuildContext context) async { - Provider.of(context, listen: false).api.system.backupConfig() + context.read().api.system.backupConfig() .then((_) => LSSnackBar( context: context, title: 'Backing Up Configuration${Constants.TEXT_ELLIPSIS}', message: 'Backing up your configuration in the background', )) .catchError((error, trace) { - Logger.error( + LunaLogger.error( 'Tautulli', '_backupConfig', 'Failed to backup configuration', @@ -49,14 +49,14 @@ class TautulliGlobalSettings extends StatelessWidget { } Future _backupDB(BuildContext context) async { - Provider.of(context, listen: false).api.system.backupDB() + context.read().api.system.backupDB() .then((_) => LSSnackBar( context: context, title: 'Backing Up Database${Constants.TEXT_ELLIPSIS}', message: 'Backing up your database in the background', )) .catchError((error, trace) { - Logger.error( + LunaLogger.error( 'Tautulli', '_backupDB', 'Failed to backup database', @@ -73,14 +73,14 @@ class TautulliGlobalSettings extends StatelessWidget { } Future _deleteCache(BuildContext context) async { - Provider.of(context, listen: false).api.system.deleteCache() + context.read().api.system.deleteCache() .then((_) => LSSnackBar( context: context, title: 'Deleting Cache${Constants.TEXT_ELLIPSIS}', message: 'Tautulli cache is being deleted', )) .catchError((error, trace) { - Logger.error( + LunaLogger.error( 'Tautulli', '_deleteCache', 'Failed to delete cache', @@ -97,14 +97,14 @@ class TautulliGlobalSettings extends StatelessWidget { } Future _deleteImageCache(BuildContext context) async { - Provider.of(context, listen: false).api.system.deleteImageCache() + context.read().api.system.deleteImageCache() .then((_) => LSSnackBar( context: context, title: 'Deleting Image Cache${Constants.TEXT_ELLIPSIS}', message: 'Tautulli image cache is being deleted', )) .catchError((error, trace) { - Logger.error( + LunaLogger.error( 'Tautulli', '_deleteImageCache', 'Failed to delete image cache', @@ -121,14 +121,14 @@ class TautulliGlobalSettings extends StatelessWidget { } Future _deleteTempSessions(BuildContext context) async { - Provider.of(context, listen: false).api.activity.deleteTempSessions() + context.read().api.activity.deleteTempSessions() .then((_) => LSSnackBar( context: context, title: 'Deleting Temporary Sessions${Constants.TEXT_ELLIPSIS}', message: 'Temporary sessions are being deleted', )) .catchError((error, trace) { - Logger.error( + LunaLogger.error( 'Tautulli', '_deleteTempSessions', 'Failed to delete temporary sessions', diff --git a/lib/modules/tautulli/modules/tautulli/widgets/navigation_bar.dart b/lib/modules/tautulli/modules/tautulli/widgets/navigation_bar.dart index c83a013f..9470f21b 100644 --- a/lib/modules/tautulli/modules/tautulli/widgets/navigation_bar.dart +++ b/lib/modules/tautulli/modules/tautulli/widgets/navigation_bar.dart @@ -63,7 +63,7 @@ class _State extends State { padding: EdgeInsets.fromLTRB(18.0, 5.0, 12.0, 5.0), duration: Duration(milliseconds: Constants.UI_NAVIGATION_SPEED), tabBackgroundColor: Theme.of(context).canvasColor, - activeColor: LSColors.accent, + activeColor: LunaColours.accent, tabs: [ GButton( icon: TautulliNavigationBar.icons[0], @@ -72,19 +72,19 @@ class _State extends State { textStyle: TextStyle( fontWeight: FontWeight.w600, fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - color: LSColors.accent, + color: LunaColours.accent, ), leading: FutureBuilder( future: state.activity, builder: (BuildContext context, AsyncSnapshot snapshot) => Badge( - badgeColor: LSColors.accent.withOpacity(0.65), + badgeColor: LunaColours.accent.withOpacity(0.65), elevation: 0, animationDuration: Duration(milliseconds: Constants.UI_NAVIGATION_SPEED), animationType: BadgeAnimationType.fade, shape: BadgeShape.circle, - position: BadgePosition.topRight( + position: BadgePosition.topEnd( top: -15, - right: -15, + end: -15, ), badgeContent: Text( snapshot.hasData @@ -95,7 +95,7 @@ class _State extends State { child: Icon( TautulliNavigationBar.icons[0], color: _index == 0 - ? LSColors.accent + ? LunaColours.accent : Colors.white, ), showBadge: state.enabled && _index != 0 && snapshot.hasData && snapshot.data.streamCount > 0, @@ -109,7 +109,7 @@ class _State extends State { textStyle: TextStyle( fontWeight: FontWeight.w600, fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - color: LSColors.accent, + color: LunaColours.accent, ), ), GButton( @@ -119,7 +119,7 @@ class _State extends State { textStyle: TextStyle( fontWeight: FontWeight.w600, fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - color: LSColors.accent, + color: LunaColours.accent, ), ), GButton( @@ -129,7 +129,7 @@ class _State extends State { textStyle: TextStyle( fontWeight: FontWeight.w600, fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - color: LSColors.accent, + color: LunaColours.accent, ), ), ], @@ -141,7 +141,7 @@ class _State extends State { ), decoration: BoxDecoration( color: Theme.of(context).primaryColor, - //LSColors.secondary, + //LunaColours.secondary, ), ), ); diff --git a/lib/modules/tautulli/modules/users/route.dart b/lib/modules/tautulli/modules/users/route.dart index fc6448d4..395cbf55 100644 --- a/lib/modules/tautulli/modules/users/route.dart +++ b/lib/modules/tautulli/modules/users/route.dart @@ -21,9 +21,8 @@ class _State extends State with AutomaticKeepAliveClientMixi bool get wantKeepAlive => true; Future _refresh() async { - TautulliState _state = Provider.of(context, listen: false); - _state.resetUsers(); - await _state.users; + context.read().resetUsers(); + await context.read().users; } @override @@ -51,7 +50,7 @@ class _State extends State with AutomaticKeepAliveClientMixi builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( 'TautulliUsersRoute', '_body', 'Unable to fetch Tautulli users', diff --git a/lib/modules/tautulli/modules/users/widgets/user_tile.dart b/lib/modules/tautulli/modules/users/widgets/user_tile.dart index 15dcba26..cc6051f1 100644 --- a/lib/modules/tautulli/modules/users/widgets/user_tile.dart +++ b/lib/modules/tautulli/modules/users/widgets/user_tile.dart @@ -29,19 +29,18 @@ class TautulliUserTile extends StatelessWidget { ), decoration: user.thumb != null && user.thumb.isNotEmpty ? LSCardBackground( - darken: true, - uri: Provider.of(context, listen: false).getImageURLFromPath( + uri: context.watch().getImageURLFromPath( user.thumb, width: MediaQuery.of(context).size.width.truncate(), ), - headers: Provider.of(context, listen: false).headers.cast(), + headers: context.watch().headers.cast(), ) : null, ); Widget _userThumb(BuildContext context) => LSNetworkImage( - url: Provider.of(context, listen: false).getImageURLFromPath(user.userThumb), - headers: Provider.of(context, listen: false).headers.cast(), + url: context.watch().getImageURLFromPath(user.userThumb), + headers: context.watch().headers.cast(), placeholder: 'assets/images/tautulli/nouserthumb.png', height: _imageDimension, width: _imageDimension, diff --git a/lib/modules/tautulli/modules/users_details/route.dart b/lib/modules/tautulli/modules/users_details/route.dart index 54246f71..ea55f62a 100644 --- a/lib/modules/tautulli/modules/users_details/route.dart +++ b/lib/modules/tautulli/modules/users_details/route.dart @@ -10,32 +10,17 @@ class TautulliUserDetailsRouter { static Future navigateTo(BuildContext context, { @required int userId, - }) async => TautulliRouter.router.navigateTo( + }) async => LunaRouter.router.navigateTo( context, route(userId: userId), ); - static String route({ - String profile, - @required int userId, - }) => [ - ROUTE_NAME.replaceFirst(':userid', userId.toString()), - if(profile != null) '/$profile', - ].join(); + static String route({ @required int userId }) => ROUTE_NAME.replaceFirst(':userid', userId.toString()); static void defineRoutes(Router router) { - router.define( - ROUTE_NAME + '/:profile', - handler: Handler(handlerFunc: (context, params) => _TautulliUserDetailsRoute( - profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null, - userId: int.tryParse(params['userid'][0]) ?? -1, - )), - transitionType: LunaRouter.transitionType, - ); router.define( ROUTE_NAME, handler: Handler(handlerFunc: (context, params) => _TautulliUserDetailsRoute( - profile: null, userId: int.tryParse(params['userid'][0]) ?? -1, )), transitionType: LunaRouter.transitionType, @@ -47,12 +32,10 @@ class TautulliUserDetailsRouter { class _TautulliUserDetailsRoute extends StatefulWidget { final int userId; - final String profile; _TautulliUserDetailsRoute({ Key key, @required this.userId, - @required this.profile, }): super(key: key); @override @@ -71,9 +54,8 @@ class _State extends State<_TautulliUserDetailsRoute> { } Future _refresh() async { - TautulliState _global = Provider.of(context, listen: false); - _global.resetUsers(); - await _global.users; + context.read().resetUsers(); + await context.read().users; } TautulliTableUser _findUser(TautulliUsersTable users) { @@ -91,7 +73,11 @@ class _State extends State<_TautulliUserDetailsRoute> { body: _body, ); - Widget get _appBar => LSAppBar(title: 'User Details'); + Widget get _appBar => LunaAppBar( + context: context, + title: 'User Details', + popUntil: '/tautulli', + ); Widget get _bottomNavigationBar => TautulliUserDetailsNavigationBar(pageController: _pageController); @@ -109,7 +95,7 @@ class _State extends State<_TautulliUserDetailsRoute> { builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( '_TautulliUserDetailsRoute', '_body', 'Unable to pull Tautulli user table', diff --git a/lib/modules/tautulli/modules/users_details/widgets/history.dart b/lib/modules/tautulli/modules/users_details/widgets/history.dart index e3e0b638..547a900b 100644 --- a/lib/modules/tautulli/modules/users_details/widgets/history.dart +++ b/lib/modules/tautulli/modules/users_details/widgets/history.dart @@ -30,16 +30,14 @@ class _State extends State with AutomaticKeepAliveCl } Future _refresh() async { - TautulliState _global = Provider.of(context, listen: false); - TautulliLocalState _local = Provider.of(context, listen: false); - _local.setUserHistory( + context.read().setUserHistory( widget.user.userId, - _global.api.history.getHistory( + context.read().api.history.getHistory( userId: widget.user.userId, length: TautulliDatabaseValue.CONTENT_LOAD_LENGTH.data, ), ); - await _local.userHistory[widget.user.userId]; + await context.read().userHistory[widget.user.userId]; } @override @@ -55,11 +53,11 @@ class _State extends State with AutomaticKeepAliveCl refreshKey: _refreshKey, onRefresh: _refresh, child: FutureBuilder( - future: Provider.of(context).userHistory[widget.user.userId], + future: context.watch().userHistory[widget.user.userId], builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( 'TautulliUserDetailsHistory', '_body', 'Unable to fetch Tautulli user history: ${widget.user.userId}', diff --git a/lib/modules/tautulli/modules/users_details/widgets/ip_addresses.dart b/lib/modules/tautulli/modules/users_details/widgets/ip_addresses.dart index 40c95045..53bebc1f 100644 --- a/lib/modules/tautulli/modules/users_details/widgets/ip_addresses.dart +++ b/lib/modules/tautulli/modules/users_details/widgets/ip_addresses.dart @@ -30,13 +30,11 @@ class _State extends State with AutomaticKeepAli } Future _refresh() async { - TautulliState _global = Provider.of(context, listen: false); - TautulliLocalState _local = Provider.of(context, listen: false); - _local.setUserIPs( + context.read().setUserIPs( widget.user.userId, - _global.api.users.getUserIPs(userId: widget.user.userId), + context.read().api.users.getUserIPs(userId: widget.user.userId), ); - await _local.userIPs[widget.user.userId]; + await context.read().userIPs[widget.user.userId]; } @override @@ -52,11 +50,11 @@ class _State extends State with AutomaticKeepAli refreshKey: _refreshKey, onRefresh: _refresh, child: FutureBuilder( - future: Provider.of(context).userIPs[widget.user.userId], + future: context.watch().userIPs[widget.user.userId], builder: (context, AsyncSnapshot snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( 'TautulliUserDetailsIPAddresses', '_body', 'Unable to fetch Tautulli user IP addresses: ${widget.user.userId}', @@ -112,12 +110,11 @@ class _State extends State with AutomaticKeepAli ), decoration: record.thumb != null && record.thumb.isNotEmpty ? LSCardBackground( - uri: Provider.of(context, listen: false).getImageURLFromPath( + uri: context.read().getImageURLFromPath( record.thumb ?? '', width: MediaQuery.of(context).size.width.truncate(), ), - headers: Provider.of(context, listen: false).headers, - darken: true, + headers: context.read().headers, ) : null, padContent: true, diff --git a/lib/modules/tautulli/modules/users_details/widgets/profile.dart b/lib/modules/tautulli/modules/users_details/widgets/profile.dart index 2efca2ff..fbf9f337 100644 --- a/lib/modules/tautulli/modules/users_details/widgets/profile.dart +++ b/lib/modules/tautulli/modules/users_details/widgets/profile.dart @@ -33,29 +33,27 @@ class _State extends State with AutomaticKeepAliveCl } Future _refresh() async { - TautulliState _global = Provider.of(context, listen: false); - TautulliLocalState _local = Provider.of(context, listen: false); // Initial load or refresh of the user profile data - _local.setUserProfile( + context.read().setUserProfile( widget.user.userId, - _global.api.users.getUser(userId: widget.user.userId), + context.read().api.users.getUser(userId: widget.user.userId), ); // Initial load or refresh of the user watch stats - _local.setUserWatchStats( + context.read().setUserWatchStats( widget.user.userId, - _global.api.users.getUserWatchTimeStats(userId: widget.user.userId, queryDays: [1, 7, 30, 0]), + context.read().api.users.getUserWatchTimeStats(userId: widget.user.userId, queryDays: [1, 7, 30, 0]), ); // Initial load or refresh of the user player stats - _local.setUserPlayerStats( + context.read().setUserPlayerStats( widget.user.userId, - _global.api.users.getUserPlayerStats(userId: widget.user.userId), + context.read().api.users.getUserPlayerStats(userId: widget.user.userId), ); setState(() => _initialLoad = true); // This await keeps the refresh indicator showing until the data is loaded await Future.wait([ - _local.userProfile[widget.user.userId], - _local.userWatchStats[widget.user.userId], - _local.userPlayerStats[widget.user.userId], + context.read().userProfile[widget.user.userId], + context.read().userWatchStats[widget.user.userId], + context.read().userPlayerStats[widget.user.userId], ]); } @@ -74,14 +72,14 @@ class _State extends State with AutomaticKeepAliveCl onRefresh: _refresh, child: FutureBuilder( future: Future.wait([ - Provider.of(context).userProfile[widget.user.userId], - Provider.of(context).userWatchStats[widget.user.userId], - Provider.of(context).userPlayerStats[widget.user.userId], + context.watch().userProfile[widget.user.userId], + context.watch().userWatchStats[widget.user.userId], + context.watch().userPlayerStats[widget.user.userId], ]), builder: (context, AsyncSnapshot> snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( 'TautulliUserDetailsProfile', '_body', 'Unable to fetch Tautulli user: ${widget.user.userId}', diff --git a/lib/modules/tautulli/modules/users_details/widgets/synced_items.dart b/lib/modules/tautulli/modules/users_details/widgets/synced_items.dart index 9e23ab7f..7d635272 100644 --- a/lib/modules/tautulli/modules/users_details/widgets/synced_items.dart +++ b/lib/modules/tautulli/modules/users_details/widgets/synced_items.dart @@ -30,13 +30,11 @@ class _State extends State with AutomaticKeepAli } Future _refresh() async { - TautulliState _global = Provider.of(context, listen: false); - TautulliLocalState _local = Provider.of(context, listen: false); - _local.setUserSyncedItems( + context.read().setUserSyncedItems( widget.user.userId, - _global.api.libraries.getSyncedItems(userId: widget.user.userId), + context.read().api.libraries.getSyncedItems(userId: widget.user.userId), ); - await _local.userSyncedItems[widget.user.userId]; + await context.read().userSyncedItems[widget.user.userId]; } @override @@ -52,11 +50,11 @@ class _State extends State with AutomaticKeepAli refreshKey: _refreshKey, onRefresh: _refresh, child: FutureBuilder( - future: Provider.of(context).userSyncedItems[widget.user.userId], + future: context.watch().userSyncedItems[widget.user.userId], builder: (context, AsyncSnapshot> snapshot) { if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( + LunaLogger.error( 'TautulliUserDetailsSyncedItems', '_body', 'Unable to fetch Tautulli user synced items: ${widget.user.userId}', diff --git a/lib/modules/wake_on_lan/core/api.dart b/lib/modules/wake_on_lan/core/api.dart index 0b5b109f..2f51c585 100644 --- a/lib/modules/wake_on_lan/core/api.dart +++ b/lib/modules/wake_on_lan/core/api.dart @@ -7,7 +7,7 @@ class WakeOnLANAPI extends API { WakeOnLANAPI._internal(this._values); factory WakeOnLANAPI.from(ProfileHiveObject profile) => WakeOnLANAPI._internal(profile.getWakeOnLAN()); - void logWarning(String methodName, String text) => Logger.warning( + void logWarning(String methodName, String text) => LunaLogger.warning( 'package:lunasea/modules/wake_on_lan/core/api.dart', methodName, 'Wake on LAN: $text', @@ -15,7 +15,7 @@ class WakeOnLANAPI extends API { void logError(String methodName, String text, Object error, StackTrace trace, { bool uploadToSentry = true, - }) => Logger.error( + }) => LunaLogger.error( 'package:lunasea/modules/wake_on_lan/core/api.dart', methodName, 'Wake on LAN: $text', diff --git a/lib/modules/wake_on_lan/core/constants.dart b/lib/modules/wake_on_lan/core/constants.dart index 7da05b13..7424b9f4 100644 --- a/lib/modules/wake_on_lan/core/constants.dart +++ b/lib/modules/wake_on_lan/core/constants.dart @@ -6,12 +6,12 @@ class WakeOnLANConstants { static const MODULE_KEY = 'wake_on_lan'; - static const ModuleMap MODULE_MAP = ModuleMap( + static const LunaModuleMap MODULE_MAP = LunaModuleMap( name: 'Wake on LAN', description: 'Wake a Sleeping Machine', settingsDescription: 'Configure Wake on LAN', icon: Icons.settings_remote, route: null, - color: Color(Constants.ACCENT_COLOR), + color: Color(LunaColours.ACCENT_COLOR), ); } diff --git a/pubspec.lock b/pubspec.lock index be15f0d8..abde1894 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -9,7 +9,7 @@ packages: source: hosted version: "6.0.0" analyzer: - dependency: "direct overridden" + dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" @@ -49,7 +49,7 @@ packages: name: badges url: "https://pub.dartlang.org" source: hosted - version: "1.1.1" + version: "1.1.3" build: dependency: transitive description: @@ -112,7 +112,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.2" + version: "1.1.0-nullsafety.3" charcode: dependency: transitive description: @@ -154,7 +154,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0-nullsafety.2" + version: "1.15.0-nullsafety.3" color: dependency: transitive description: @@ -168,7 +168,7 @@ packages: name: connectivity url: "https://pub.dartlang.org" source: hosted - version: "0.4.9+2" + version: "0.4.9+3" connectivity_for_web: dependency: transitive description: @@ -210,7 +210,7 @@ packages: name: csslib url: "https://pub.dartlang.org" source: hosted - version: "0.16.1" + version: "0.16.2" dart_style: dependency: transitive description: @@ -395,7 +395,7 @@ packages: name: google_nav_bar url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "3.0.1" graphs: dependency: transitive description: @@ -423,7 +423,7 @@ packages: name: hive_generator url: "https://pub.dartlang.org" source: hosted - version: "0.7.1" + version: "0.7.2+1" html: dependency: transitive description: @@ -472,7 +472,7 @@ packages: name: in_app_purchase url: "https://pub.dartlang.org" source: hosted - version: "0.3.4+8" + version: "0.3.4+10" intl: dependency: "direct main" description: @@ -500,7 +500,7 @@ packages: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.1.0" logging: dependency: transitive description: @@ -521,7 +521,7 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.2" + version: "1.3.0-nullsafety.3" mime: dependency: transitive description: @@ -591,7 +591,7 @@ packages: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "1.6.16" + version: "1.6.18" path_provider_linux: dependency: transitive description: @@ -619,7 +619,7 @@ packages: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "0.0.3" + version: "0.0.4+1" pedantic: dependency: transitive description: @@ -633,7 +633,7 @@ packages: name: percent_indicator url: "https://pub.dartlang.org" source: hosted - version: "2.1.6" + version: "2.1.7+4" permission_handler: dependency: "direct main" description: @@ -724,7 +724,7 @@ packages: name: quick_actions url: "https://pub.dartlang.org" source: hosted - version: "0.4.0+8" + version: "0.4.0+9" quiver: dependency: transitive description: @@ -779,6 +779,13 @@ packages: description: flutter source: sdk version: "0.0.99" + sonarr: + dependency: "direct main" + description: + name: sonarr + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.2-pre.1" source_gen: dependency: transitive description: @@ -876,14 +883,14 @@ packages: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.2" + version: "1.3.0-nullsafety.3" url_launcher: dependency: "direct main" description: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "5.6.0" + version: "5.7.2" url_launcher_linux: dependency: transitive description: @@ -946,7 +953,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.2" + version: "2.1.0-nullsafety.3" wake_on_lan: dependency: "direct main" description: @@ -1011,5 +1018,5 @@ packages: source: hosted version: "2.2.1" sdks: - dart: ">=2.10.0-0.0.dev <2.10.0" + dart: ">=2.10.0-110 <2.11.0" flutter: ">=1.20.0 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 75960ffb..4976c1c9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,13 +1,13 @@ name: lunasea description: Self Hosted Manager -version: 4.0.0+4000103 +version: 4.1.0+40100001 publish_to: 'none' environment: sdk: ">=2.7.0 <3.0.0" dependencies: - badges: ^1.1.1 - connectivity: ^0.4.9+2 + badges: ^1.1.3 + connectivity: ^0.4.9+3 convert: ^2.1.1 dio: ^3.0.10 encrypt: ^4.0.3 @@ -23,25 +23,26 @@ dependencies: flutter_svg: ^0.18.1 fl_chart: ^0.11.1 f_logs: ^1.3.0-alpha-02 - google_nav_bar: ^3.0.0 + google_nav_bar: ^3.0.1 hive: ^1.4.4 hive_flutter: ^0.3.1 - in_app_purchase: ^0.3.4+8 + in_app_purchase: ^0.3.4+10 intl: ^0.16.1 package_info: ^0.4.3 - path_provider: ^1.6.16 - percent_indicator: ^2.1.6 + path_provider: ^1.6.18 + percent_indicator: ^2.1.7+4 permission_handler: ^5.0.1+1 provider: ^4.3.2+2 - quick_actions: ^0.4.0+8 + quick_actions: ^0.4.0+9 sentry: ^3.0.1 stack_trace: ^1.9.5 table_calendar: ^2.2.3 tuple: ^1.0.3 - url_launcher: ^5.6.0 + url_launcher: ^5.7.2 uuid: ^2.2.2 xml_parser: ^0.1.2 - # LunaTools Packages + # Comet.Tools Packages + sonarr: ^0.0.2-pre.1 tautulli: ^1.1.0 wake_on_lan: ^1.1.1+1 @@ -49,10 +50,7 @@ dev_dependencies: build_runner: ^1.10.1 flutter_launcher_icons: ^0.8.0 flutter_native_splash: ^0.1.9 - hive_generator: ^0.7.1 - -dependency_overrides: - analyzer: 0.39.14 + hive_generator: ^0.7.2+1 flutter: uses-material-design: true