mirror of
https://github.com/jagandeepbrar/lunasea.git
synced 2026-08-31 12:42:34 +00:00
[Logger] Implemented On-Device Logger (#44)
* [Packages] Added f_logs * [Logger] Initialized, captures crashes * [State, Logger] Safe-guarded setState, added logger tab in settings * [Logs] Implemented logs page, log types page, stub log view page * [Logs] Added log types page, log view page, stub log details page * [Logger] Finished details view * [Logger] Removed info logging * [Lidarr] Added logging to API calls * [Radarr] Added logging to API calls * [ListTiles] Added arrows for ListTiles that have more text * [Sonarr] Added logging to API calls * [Sonarr] Safetied some calculations * [SABnzbd] Added logging to API calls * [Sonarr] Prevent dividing by zero Closes #38
This commit is contained in:
@@ -6,6 +6,8 @@ PODS:
|
||||
- Flutter
|
||||
- path_provider (0.0.1):
|
||||
- Flutter
|
||||
- permission_handler (4.0.0):
|
||||
- Flutter
|
||||
- shared_preferences (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_macos (0.0.1):
|
||||
@@ -24,6 +26,7 @@ DEPENDENCIES:
|
||||
- Flutter (from `Flutter`)
|
||||
- package_info (from `.symlinks/plugins/package_info/ios`)
|
||||
- path_provider (from `.symlinks/plugins/path_provider/ios`)
|
||||
- permission_handler (from `.symlinks/plugins/permission_handler/ios`)
|
||||
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
|
||||
- shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`)
|
||||
- shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`)
|
||||
@@ -40,6 +43,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/package_info/ios"
|
||||
path_provider:
|
||||
:path: ".symlinks/plugins/path_provider/ios"
|
||||
permission_handler:
|
||||
:path: ".symlinks/plugins/permission_handler/ios"
|
||||
shared_preferences:
|
||||
:path: ".symlinks/plugins/shared_preferences/ios"
|
||||
shared_preferences_macos:
|
||||
@@ -58,6 +63,7 @@ SPEC CHECKSUMS:
|
||||
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
|
||||
package_info: 48b108e75b8802c2d5e126f208ef540561c98aef
|
||||
path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d
|
||||
permission_handler: 0fb88b8a3c23e31e5de45b8f1bb8b7de28cf8941
|
||||
shared_preferences: 430726339841afefe5142b9c1f50cb6bd7793e01
|
||||
shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087
|
||||
shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9
|
||||
|
||||
@@ -59,5 +59,12 @@
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<!--
|
||||
Needed for f_logs: https://pub.dev/packages/f_logs
|
||||
-->
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>LunaSea would like to save photos from the app to your gallery</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>LunaSea would like to access your photo gallery for uploading images to the app</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -2,9 +2,18 @@ import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:lunasea/configuration/values.dart';
|
||||
import 'package:lunasea/logic/automation/lidarr.dart';
|
||||
import 'package:lunasea/system/logger.dart';
|
||||
|
||||
class LidarrAPI {
|
||||
LidarrAPI._();
|
||||
|
||||
static void logWarning(String methodName, String text) {
|
||||
Logger.warning('package:lunasea/logic/automation/lidarr/api.dart', methodName, 'Lidarr: $text');
|
||||
}
|
||||
|
||||
static void logError(String methodName, String text, Object error) {
|
||||
Logger.error('package:lunasea/logic/automation/lidarr/api.dart', methodName, 'Lidarr: $text', error, StackTrace.current);
|
||||
}
|
||||
|
||||
static Future<bool> testConnection(List<dynamic> values) async {
|
||||
try {
|
||||
@@ -19,8 +28,10 @@ class LidarrAPI {
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logError('testConnection', 'Connection test failed', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('testConnection', 'Connection test failed');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -37,10 +48,14 @@ class LidarrAPI {
|
||||
if(response.statusCode == 200) {
|
||||
List body = json.decode(response.body);
|
||||
return body.length ?? 0;
|
||||
} else {
|
||||
logError('getArtistCount', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getArtistCount', 'Failed to fetch artist count', e);
|
||||
return -1;
|
||||
}
|
||||
logWarning('getArtistCount', 'Failed to fetch artist count');
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -79,11 +94,15 @@ class LidarrAPI {
|
||||
));
|
||||
}
|
||||
return entries;
|
||||
} else {
|
||||
logError('getAllArtists', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getAllArtists', 'Failed to fetch artists', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getAllArtists', 'Failed to fetch artists');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -109,10 +128,14 @@ class LidarrAPI {
|
||||
if(body.containsKey('status')) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('refreshArtist', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('refreshArtist', 'Failed to refresh artist ($artistID)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('refreshArtist', 'Failed to refresh artist ($artistID)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -147,11 +170,15 @@ class LidarrAPI {
|
||||
body['links'] ?? [],
|
||||
body['albumFolder'] ?? false,
|
||||
);
|
||||
} else {
|
||||
logError('getArtist', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getArtist', 'Failed to fetch artist ($artistID)', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getArtist', 'Failed to fetch artist ($artistID)');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -170,10 +197,14 @@ class LidarrAPI {
|
||||
if(body.length == 0) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('removeArtist', '<DELETE> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('removeArtist', 'Failed to remove artist ($artistID)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('removeArtist', 'Failed to remove artist ($artistID)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -205,11 +236,17 @@ class LidarrAPI {
|
||||
);
|
||||
if(response.statusCode == 202) {
|
||||
return true;
|
||||
} else {
|
||||
logError('editArtist', '<PUT> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} else {
|
||||
logError('editArtist', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('editArtist', 'Failed to edit artist ($artistID)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('editArtist', 'Failed to edit artist ($artistID)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -248,11 +285,15 @@ class LidarrAPI {
|
||||
return b.releaseDateObject.compareTo(a.releaseDateObject);
|
||||
});
|
||||
return entries;
|
||||
} else {
|
||||
logError('getArtistAlbums', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getArtistAlbums', 'Failed to fetch albums ($artistID)', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getArtistAlbums', 'Failed to fetch albums ($artistID)');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -278,11 +319,15 @@ class LidarrAPI {
|
||||
body[0]['statistics'] == null ? 0 : body[0]['statistics']['percentOfTracks'] ?? 0,
|
||||
body[0]['releaseDate'] ?? '',
|
||||
);
|
||||
} else {
|
||||
logError('getAlbum', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getAlbum', 'Failed to fetch album ($albumID)', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getAlbum', 'Failed to fetch album ($albumID)');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -310,10 +355,14 @@ class LidarrAPI {
|
||||
));
|
||||
}
|
||||
return entries;
|
||||
} else {
|
||||
logError('getAlbumTracks', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getAlbumTracks', 'Failed to fetch album tracks ($albumID)', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getAlbumTracks', 'Failed to fetch album tracks ($albumID)');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -337,10 +386,14 @@ class LidarrAPI {
|
||||
);
|
||||
}
|
||||
return _entries;
|
||||
} else {
|
||||
logError('getQualityProfiles', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getQualityProfiles', 'Failed to fetch quality profiles', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getQualityProfiles', 'Failed to fetch quality profiles');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -364,10 +417,14 @@ class LidarrAPI {
|
||||
);
|
||||
}
|
||||
return _entries;
|
||||
} else {
|
||||
logError('getMetadataProfiles', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getMetadataProfiles', 'Failed to fetch metadata profiles', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getMetadataProfiles', 'Failed to fetch metadata profiles');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -457,10 +514,14 @@ class LidarrAPI {
|
||||
}
|
||||
}
|
||||
return _entries;
|
||||
} else {
|
||||
logError('getHistory', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getHistory', 'Failed to fetch history', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getHistory', 'Failed to fetch history');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -488,10 +549,14 @@ class LidarrAPI {
|
||||
));
|
||||
}
|
||||
return entries;
|
||||
} else {
|
||||
logError('getMissing', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getMissing', 'Failed to fetch missing albums', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getMissing', 'Failed to fetch missing albums');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -517,10 +582,14 @@ class LidarrAPI {
|
||||
if(body.containsKey('status')) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('searchAlbums', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('searchAlbums', 'Failed to search for albums (${albums.toString()})', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('searchAlbums', 'Failed to search for albums (${albums.toString()})');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -547,10 +616,14 @@ class LidarrAPI {
|
||||
if(body.containsKey('status')) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('searchAllMissing', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('searchAllMissing', 'Failed to search for all missing albums', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('searchAllMissing', 'Failed to search for all missing albums');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -575,10 +648,14 @@ class LidarrAPI {
|
||||
if(body.containsKey('status')) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('updateLibrary', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('updateLibrary', 'Failed to update library', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('updateLibrary', 'Failed to update library');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -603,10 +680,14 @@ class LidarrAPI {
|
||||
if(body.containsKey('status')) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('triggerRssSync', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('triggerRssSync', 'Failed to trigger RSS sync', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('triggerRssSync', 'Failed to trigger RSS sync');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -631,10 +712,14 @@ class LidarrAPI {
|
||||
if(body.containsKey('status')) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('triggerBackup', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('triggerBackup', 'Failed to backup database', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('triggerBackup', 'Failed to backup database');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -661,11 +746,17 @@ class LidarrAPI {
|
||||
);
|
||||
if(response.statusCode == 202) {
|
||||
return true;
|
||||
} else {
|
||||
logError('toggleArtistMonitored', '<PUT> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} else {
|
||||
logError('toggleArtistMonitored', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('toggleArtistMonitored', 'Failed to toggle artist monitored status ($artistID)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('toggleArtistMonitored', 'Failed to toggle artist monitored status ($artistID)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -692,11 +783,17 @@ class LidarrAPI {
|
||||
);
|
||||
if(response.statusCode == 202) {
|
||||
return true;
|
||||
} else {
|
||||
logError('downloadRelease', '<PUT> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} else {
|
||||
logError('downloadRelease', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('toggleAlbumMonitored', 'Failed to toggle album monitored status ($albumID)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('toggleAlbumMonitored', 'Failed to toggle album monitored status ($albumID)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -728,10 +825,14 @@ class LidarrAPI {
|
||||
));
|
||||
}
|
||||
return entries;
|
||||
} else {
|
||||
logError('searchArtists', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('searchArtists', 'Failed to search ($search)', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('searchArtists', 'Failed to search ($search)');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -762,20 +863,24 @@ class LidarrAPI {
|
||||
);
|
||||
if(response.statusCode == 201) {
|
||||
return true;
|
||||
} else {
|
||||
logError('addArtist', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('addArtist', 'Failed to add artist (${entry.title})', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('addArtist', 'Failed to add artist (${entry.title})');
|
||||
return false;
|
||||
}
|
||||
|
||||
static Future<List<LidarrReleaseEntry>> getReleases(int albumId) async {
|
||||
static Future<List<LidarrReleaseEntry>> getReleases(int albumID) async {
|
||||
List<dynamic> values = Values.lidarrValues;
|
||||
if(values[0] == false) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
String uri = '${values[1]}/api/v1/release?apikey=${values[2]}&albumId=$albumId';
|
||||
String uri = '${values[1]}/api/v1/release?apikey=${values[2]}&albumId=$albumID';
|
||||
http.Response response = await http.get(
|
||||
Uri.encodeFull(uri),
|
||||
);
|
||||
@@ -801,10 +906,14 @@ class LidarrAPI {
|
||||
));
|
||||
}
|
||||
return entries;
|
||||
} else {
|
||||
logError('getReleases', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getReleases', 'Failed to fetch releases ($albumID)', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getReleases', 'Failed to fetch releases ($albumID)');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -827,10 +936,14 @@ class LidarrAPI {
|
||||
);
|
||||
if(response.statusCode == 200) {
|
||||
return true;
|
||||
} else {
|
||||
logError('downloadRelease', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('downloadRelease', 'Failed to download release ($guid)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('downloadRelease', 'Failed to download release ($guid)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -854,10 +967,14 @@ class LidarrAPI {
|
||||
));
|
||||
}
|
||||
return _entries;
|
||||
} else {
|
||||
logError('getRootFolders', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getRootFolders', 'Failed to fetch root folders', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getRootFolders', 'Failed to fetch root folders');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,18 @@ import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:lunasea/configuration/values.dart';
|
||||
import 'package:lunasea/logic/automation/radarr.dart';
|
||||
import 'package:lunasea/system/logger.dart';
|
||||
|
||||
class RadarrAPI {
|
||||
RadarrAPI._();
|
||||
|
||||
static void logWarning(String methodName, String text) {
|
||||
Logger.warning('package:lunasea/logic/automation/radarr/api.dart', methodName, 'Radarr: $text');
|
||||
}
|
||||
|
||||
static void logError(String methodName, String text, Object error) {
|
||||
Logger.error('package:lunasea/logic/automation/radarr/api.dart', methodName, 'Radarr: $text', error, StackTrace.current);
|
||||
}
|
||||
|
||||
static Future<bool> testConnection(List<dynamic> values) async {
|
||||
try {
|
||||
@@ -19,8 +28,10 @@ class RadarrAPI {
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logError('testConnection', 'Connection test failed', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('testConnection', 'Connection test failed');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -53,10 +64,14 @@ class RadarrAPI {
|
||||
);
|
||||
if(response.statusCode == 201) {
|
||||
return true;
|
||||
} else {
|
||||
logError('addMovie', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('addMovie', 'Failed to add movie (${entry.title})', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('addMovie', 'Failed to add movie (${entry.title})');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -82,10 +97,14 @@ class RadarrAPI {
|
||||
if(body.containsKey('status')) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('refreshMovie', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('refreshMovie', 'Failed to refresh movie ($id)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('refreshMovie', 'Failed to refresh movie ($id)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -110,10 +129,14 @@ class RadarrAPI {
|
||||
if(body.containsKey('status')) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('updateLibrary', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('updateLibrary', 'Failed to update library', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('updateLibrary', 'Failed to update library');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -138,10 +161,14 @@ class RadarrAPI {
|
||||
if(body.containsKey('status')) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('triggerRssSync', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('triggerRssSync', 'Failed to trigger RSS sync', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('triggerRssSync', 'Failed to trigger RSS sync');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -166,10 +193,14 @@ class RadarrAPI {
|
||||
if(body.containsKey('status')) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('triggerBackup', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('triggerBackup', 'Failed to backup database', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('triggerBackup', 'Failed to backup database');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -200,11 +231,17 @@ class RadarrAPI {
|
||||
);
|
||||
if(response.statusCode == 202) {
|
||||
return true;
|
||||
} else {
|
||||
logError('editMovie', '<PUT> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} else {
|
||||
logError('editMovie', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('editMovie', 'Failed to edit movie ($movieID)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('editMovie', 'Failed to edit movie ($movieID)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -223,10 +260,14 @@ class RadarrAPI {
|
||||
if(body.length == 0) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('removeMovie', '<DELETE> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('removeMovie', 'Failed to remove movie ($id)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('removeMovie', 'Failed to remove movie ($id)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -243,10 +284,14 @@ class RadarrAPI {
|
||||
if(response.statusCode == 200) {
|
||||
List body = json.decode(response.body);
|
||||
return body.length ?? 0;
|
||||
} else {
|
||||
logError('getMovieCount', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getMovieCount', 'Failed to fetch movie count', e);
|
||||
return -1;
|
||||
}
|
||||
logWarning('getMovieCount', 'Failed to fetch movie count');
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -294,11 +339,15 @@ class RadarrAPI {
|
||||
);
|
||||
}
|
||||
return entries;
|
||||
} else {
|
||||
logError('getAllMovies', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getAllMovies', 'Failed to fetch all movies', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getAllMovies', 'Failed to fetch all movies');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -340,11 +389,15 @@ class RadarrAPI {
|
||||
body['tmdbId'] ?? 0,
|
||||
body['pathState'] == 'static' ? true : false,
|
||||
);
|
||||
} else {
|
||||
logError('getMovie', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getMovie', 'Failed to fetch movie ($id)', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getMovie', 'Failed to fetch movie ($id)');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -363,10 +416,14 @@ class RadarrAPI {
|
||||
if(body.length == 0) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('removeMovieFile', '<DELETE> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('removeMovieFile', 'Failed to remove movie file ($id)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('removeMovieFile', 'Failed to remove movie file ($id)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -456,11 +513,15 @@ class RadarrAPI {
|
||||
return a.inCinemasObject.compareTo(b.inCinemasObject);
|
||||
});
|
||||
return [_availableEntries, _cinemaEntries, _announcedEntries].expand((x) => x).toList();
|
||||
} else {
|
||||
logError('getMissing', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getMissing', 'Failed to fetch missing movies', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getMissing', 'Failed to fetch missing movies');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -528,10 +589,14 @@ class RadarrAPI {
|
||||
}
|
||||
}
|
||||
return _entries;
|
||||
} else {
|
||||
logError('getHistory', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getHistory', 'Failed to fetch history', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getHistory', 'Failed to fetch history');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -557,10 +622,14 @@ class RadarrAPI {
|
||||
if(body.containsKey('status')) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('searchMissingMovies', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('searchMissingMovies', 'Failed to search for missing movies (${movieIDs.toString()})', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('searchMissingMovies', 'Failed to search for missing movies (${movieIDs.toString()})');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -587,10 +656,14 @@ class RadarrAPI {
|
||||
if(body.containsKey('status')) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('searchAllMissing', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('searchAllMissing', 'Failed to search for all missing movies', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('searchAllMissing', 'Failed to search for all missing movies');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -617,11 +690,17 @@ class RadarrAPI {
|
||||
);
|
||||
if(response.statusCode == 202) {
|
||||
return true;
|
||||
} else {
|
||||
logError('toggleMovieMonitored', '<PUT> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} else {
|
||||
logError('toggleMovieMonitored', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('toggleMovieMonitored', 'Failed to toggle movie monitored status ($movieID)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('toggleMovieMonitored', 'Failed to toggle movie monitored status ($movieID)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -645,20 +724,24 @@ class RadarrAPI {
|
||||
);
|
||||
}
|
||||
return _entries;
|
||||
} else {
|
||||
logError('getQualityProfiles', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getQualityProfiles', 'Failed to fetch quality profiles', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getQualityProfiles', 'Failed to fetch quality profiles');
|
||||
return null;
|
||||
}
|
||||
|
||||
static Future<List<RadarrReleaseEntry>> getReleases(int movieId) async {
|
||||
static Future<List<RadarrReleaseEntry>> getReleases(int movieID) async {
|
||||
List<dynamic> values = Values.radarrValues;
|
||||
if(values[0] == false) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
String uri = '${values[1]}/api/release?apikey=${values[2]}&movieId=$movieId';
|
||||
String uri = '${values[1]}/api/release?apikey=${values[2]}&movieId=$movieID';
|
||||
http.Response response = await http.get(
|
||||
Uri.encodeFull(uri),
|
||||
);
|
||||
@@ -684,10 +767,14 @@ class RadarrAPI {
|
||||
));
|
||||
}
|
||||
return _entries;
|
||||
} else {
|
||||
logError('getReleases', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getReleases', 'Failed to get releases ($movieID)', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getReleases', 'Failed to get releases ($movieID)');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -710,10 +797,14 @@ class RadarrAPI {
|
||||
);
|
||||
if(response.statusCode == 200) {
|
||||
return true;
|
||||
} else {
|
||||
logError('downloadRelease', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
logError('downloadRelease', 'Failed to download release ($guid)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('downloadRelease', 'Failed to download release ($guid)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -744,11 +835,15 @@ class RadarrAPI {
|
||||
));
|
||||
}
|
||||
return entries;
|
||||
} else {
|
||||
logError('searchMovies', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
return null;
|
||||
logError('searchMovies', 'Failed to search ($search)', e);
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
logWarning('searchMovies', 'Failed to search ($search)');
|
||||
return null;
|
||||
}
|
||||
|
||||
static Future<List<RadarrRootFolder>> getRootFolders() async {
|
||||
@@ -771,10 +866,14 @@ class RadarrAPI {
|
||||
));
|
||||
}
|
||||
return _entries;
|
||||
} else {
|
||||
logError('getRootFolders', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getRootFolders', 'Failed to fetch root folders', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getRootFolders', 'Failed to fetch root folders');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,18 @@ import 'package:http/http.dart' as http;
|
||||
import 'package:lunasea/configuration/values.dart';
|
||||
import 'package:lunasea/logic/automation/sonarr.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:lunasea/system/logger.dart';
|
||||
|
||||
class SonarrAPI {
|
||||
SonarrAPI._();
|
||||
|
||||
static void logWarning(String methodName, String text) {
|
||||
Logger.warning('package:lunasea/logic/automation/sonarr/api.dart', methodName, 'Sonarr: $text');
|
||||
}
|
||||
|
||||
static void logError(String methodName, String text, Object error) {
|
||||
Logger.error('package:lunasea/logic/automation/sonarr/api.dart', methodName, 'Sonarr: $text', error, StackTrace.current);
|
||||
}
|
||||
|
||||
static Future<bool> testConnection(List<dynamic> values) async {
|
||||
try {
|
||||
@@ -20,8 +29,10 @@ class SonarrAPI {
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logError('testConnection', 'Connection test failed', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('testConnection', 'Connection test failed');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -32,20 +43,24 @@ class SonarrAPI {
|
||||
}
|
||||
try {
|
||||
String uri = '${values[1]}/api/series?apikey=${values[2]}';
|
||||
http.Response response = await http.get(
|
||||
Uri.encodeFull(uri),
|
||||
);
|
||||
if(response.statusCode == 200) {
|
||||
List body = json.decode(response.body);
|
||||
return body.length ?? 0;
|
||||
}
|
||||
http.Response response = await http.get(
|
||||
Uri.encodeFull(uri),
|
||||
);
|
||||
if(response.statusCode == 200) {
|
||||
List body = json.decode(response.body);
|
||||
return body.length ?? 0;
|
||||
} else {
|
||||
logError('getSeriesCount', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getSeriesCount', 'Failed to fetch series count', e);
|
||||
return -1;
|
||||
}
|
||||
logWarning('getSeriesCount', 'Failed to fetch series count');
|
||||
return -1;
|
||||
}
|
||||
|
||||
static Future<bool> refreshSeries(int id) async {
|
||||
static Future<bool> refreshSeries(int seriesID) async {
|
||||
List<dynamic> values = Values.sonarrValues;
|
||||
if(values[0] == false) {
|
||||
return false;
|
||||
@@ -59,7 +74,7 @@ class SonarrAPI {
|
||||
},
|
||||
body: json.encode({
|
||||
'name': 'RefreshSeries',
|
||||
'seriesId': id,
|
||||
'seriesId': seriesID,
|
||||
}),
|
||||
);
|
||||
if(response.statusCode == 201) {
|
||||
@@ -67,20 +82,24 @@ class SonarrAPI {
|
||||
if(body.containsKey('status')) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('refreshSeries', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('refreshSeries', 'Failed to refresh series ($seriesID)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('refreshSeries', 'Failed to refresh series ($seriesID)');
|
||||
return false;
|
||||
}
|
||||
|
||||
static Future<bool> removeSeries(int id) async {
|
||||
static Future<bool> removeSeries(int seriesID) async {
|
||||
List<dynamic> values = Values.sonarrValues;
|
||||
if(values[0] == false) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
String uri = '${values[1]}/api/series/$id?apikey=${values[2]}';
|
||||
String uri = '${values[1]}/api/series/$seriesID?apikey=${values[2]}';
|
||||
http.Response response = await http.delete(
|
||||
Uri.encodeFull(uri),
|
||||
);
|
||||
@@ -89,10 +108,14 @@ class SonarrAPI {
|
||||
if(body.length == 0) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('removeSeries', '<DELETE> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('removeSeries', 'Failed to remove series ($seriesID)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('removeSeries', 'Failed to remove series ($seriesID)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -126,10 +149,14 @@ class SonarrAPI {
|
||||
));
|
||||
}
|
||||
return entries;
|
||||
} else {
|
||||
logError('searchSeries', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('searchSeries', 'Failed to search ($search)', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('searchSeries', 'Failed to search ($search)');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -165,10 +192,14 @@ class SonarrAPI {
|
||||
);
|
||||
if(response.statusCode == 201) {
|
||||
return true;
|
||||
} else {
|
||||
logError('addSeries', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('addSeries', 'Failed to add series (${entry.title})', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('addSeries', 'Failed to add series (${entry.title})');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -199,15 +230,21 @@ class SonarrAPI {
|
||||
);
|
||||
if(response.statusCode == 202) {
|
||||
return true;
|
||||
} else {
|
||||
logError('editSeries', '<PUT> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} else {
|
||||
logError('editSeries', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('editSeries', 'Failed to edit series ($seriesID)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('editSeries', 'Failed to edit series ($seriesID)');
|
||||
return false;
|
||||
}
|
||||
|
||||
static Future<SonarrCatalogueEntry> getSeries(int id) async {
|
||||
static Future<SonarrCatalogueEntry> getSeries(int seriesID) async {
|
||||
List<dynamic> values = Values.sonarrValues;
|
||||
if(values[0] == false) {
|
||||
return null;
|
||||
@@ -215,7 +252,7 @@ class SonarrAPI {
|
||||
try {
|
||||
Map<int, SonarrQualityProfile> _qualities = await getQualityProfiles();
|
||||
if(_qualities != null) {
|
||||
String uri = '${values[1]}/api/series/$id?apikey=${values[2]}';
|
||||
String uri = '${values[1]}/api/series/$seriesID?apikey=${values[2]}';
|
||||
http.Response response = await http.get(
|
||||
Uri.encodeFull(uri),
|
||||
);
|
||||
@@ -248,11 +285,15 @@ class SonarrAPI {
|
||||
body['profileId'] != null ? _qualities[body['qualityProfileId']].name : '',
|
||||
);
|
||||
return entry;
|
||||
} else {
|
||||
logError('getSeries', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getSeries', 'Failed to fetch series ($seriesID)', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getSeries', 'Failed to fetch series ($seriesID)');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -302,11 +343,15 @@ class SonarrAPI {
|
||||
);
|
||||
}
|
||||
return entries;
|
||||
} else {
|
||||
logError('getAllSeries', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getAllSeries', 'Failed to fetch all series', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getAllSeries', 'Failed to fetch all series');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -355,10 +400,14 @@ class SonarrAPI {
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
} else {
|
||||
logError('getUpcoming', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getUpcoming', 'Failed to fetch upcoming episodes', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getUpcoming', 'Failed to fetch upcoming episodes');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -450,10 +499,14 @@ class SonarrAPI {
|
||||
}
|
||||
}
|
||||
return _entries;
|
||||
} else {
|
||||
logError('getHistory', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getHistory', 'Failed to fetch history', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getHistory', 'Failed to fetch history');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -505,10 +558,14 @@ class SonarrAPI {
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
} else {
|
||||
logError('getEpisodes', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getEpisodes', 'Failed to fetch episodes ($seriesID, $seasonNumber)', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getEpisodes', 'Failed to fetch episodes ($seriesID, $seasonNumber)');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -534,10 +591,14 @@ class SonarrAPI {
|
||||
);
|
||||
}
|
||||
return entries;
|
||||
} else {
|
||||
logError('getQueue', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getQueue', 'Failed to fetch queue', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getQueue', 'Failed to fetch queue');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -566,10 +627,14 @@ class SonarrAPI {
|
||||
));
|
||||
}
|
||||
return entries;
|
||||
} else {
|
||||
logError('getMissing', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getMissing', 'Failed to fetch missing episodes', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getMissing', 'Failed to fetch missing episodes');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -594,10 +659,14 @@ class SonarrAPI {
|
||||
if(body.containsKey('status')) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('searchAllMissing', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('searchAllMissing', 'Failed to search for all missing episodes', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('searchAllMissing', 'Failed to search for all missing episodes');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -622,10 +691,14 @@ class SonarrAPI {
|
||||
if(body.containsKey('status')) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('updateLibrary', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('updateLibrary', 'Failed to update library', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('updateLibrary', 'Failed to update library');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -650,10 +723,14 @@ class SonarrAPI {
|
||||
if(body.containsKey('status')) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('triggerRssSync', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('triggerRssSync', 'Failed to trigger RSS sync', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('triggerRssSync', 'Failed to trigger RSS sync');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -678,10 +755,14 @@ class SonarrAPI {
|
||||
if(body.containsKey('status')) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('triggerBackup', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('triggerBackup', 'Failed to backup database', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('triggerBackup', 'Failed to backup database');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -708,10 +789,14 @@ class SonarrAPI {
|
||||
if(body.containsKey('status')) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('searchSeason', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('searchSeason', 'Failed to search for season ($seriesID, $season)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('searchSeason', 'Failed to search for season ($seriesID, $season)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -737,10 +822,14 @@ class SonarrAPI {
|
||||
if(body.containsKey('status')) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('searchEpisodes', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('searchEpisodes', 'Failed to search for episodes (${episodeIDs.toString()})', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('searchEpisodes', 'Failed to search for episodes (${episodeIDs.toString()})');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -767,11 +856,17 @@ class SonarrAPI {
|
||||
);
|
||||
if(response.statusCode == 202) {
|
||||
return true;
|
||||
} else {
|
||||
logError('toggleSeriesMonitored', '<PUT> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} else {
|
||||
logError('toggleSeriesMonitored', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('toggleSeriesMonitored', 'Failed to toggle series monitored ($seriesID)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('toggleSeriesMonitored', 'Failed to toggle series monitored ($seriesID)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -802,11 +897,17 @@ class SonarrAPI {
|
||||
);
|
||||
if(response.statusCode == 202) {
|
||||
return true;
|
||||
} else {
|
||||
logError('toggleSeasonMonitored', '<PUT> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} else {
|
||||
logError('toggleSeasonMonitored', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('toggleSeasonMonitored', 'Failed to toggle season monitored ($seriesID, $seasonID)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('toggleSeasonMonitored', 'Failed to toggle season monitored ($seriesID, $seasonID)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -830,10 +931,14 @@ class SonarrAPI {
|
||||
));
|
||||
}
|
||||
return _entries;
|
||||
} else {
|
||||
logError('getRootFolders', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getRootFolders', 'Failed to fetch root folders', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getRootFolders', 'Failed to fetch root folders');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -857,10 +962,14 @@ class SonarrAPI {
|
||||
);
|
||||
}
|
||||
return _entries;
|
||||
} else {
|
||||
logError('getQualityProfiles', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getQualityProfiles', 'Failed to fetch quality profiles', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getQualityProfiles', 'Failed to fetch quality profiles');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -896,10 +1005,14 @@ class SonarrAPI {
|
||||
));
|
||||
}
|
||||
return _entries;
|
||||
} else {
|
||||
logError('getReleases', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getReleases', 'Failed to fetch releases ($episodeId)', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getReleases', 'Failed to fetch releases ($episodeId)');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -922,10 +1035,14 @@ class SonarrAPI {
|
||||
);
|
||||
if(response.statusCode == 200) {
|
||||
return true;
|
||||
} else {
|
||||
logError('downloadRelease', '<POST> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
logError('downloadRelease', 'Failed to download release ($guid)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('downloadRelease', 'Failed to download release ($guid)');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ class SonarrEpisodeEntry {
|
||||
TextSpan get subtitle {
|
||||
if(queue != null) {
|
||||
return TextSpan(
|
||||
text: '${queue.status} (${(100-(queue.sizeLeft/queue.size)*100).abs().toInt()}%)',
|
||||
text: '${queue?.status ?? 'Unknown'} (${(100-((queue?.sizeLeft ?? 0)/(queue?.size ?? 1))*100).abs().toInt()}%)',
|
||||
style: TextStyle(
|
||||
color: Colors.blue,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
||||
@@ -2,9 +2,18 @@ import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:lunasea/configuration/values.dart';
|
||||
import 'package:lunasea/logic/clients/sabnzbd/entry.dart';
|
||||
import 'package:lunasea/system/logger.dart';
|
||||
|
||||
class SABnzbdAPI {
|
||||
SABnzbdAPI._();
|
||||
|
||||
static void logWarning(String methodName, String text) {
|
||||
Logger.warning('package:lunasea/logic/clients/sabnzbd/api.dart', methodName, 'SABnzbd: $text');
|
||||
}
|
||||
|
||||
static void logError(String methodName, String text, Object error) {
|
||||
Logger.error('package:lunasea/logic/clients/sabnzbd/api.dart', methodName, 'SABnzbd: $text', error, StackTrace.current);
|
||||
}
|
||||
|
||||
static Future<bool> testConnection(List<dynamic> values) async {
|
||||
try {
|
||||
@@ -19,8 +28,10 @@ class SABnzbdAPI {
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logError('testConnection', 'Connection test failed', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('testConnection', 'Connection test failed');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -61,10 +72,14 @@ class SABnzbdAPI {
|
||||
statisticsBody['total'] ?? 0,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
logError('getStatistics', '<GET> HTTP Status Code(s) (${status.statusCode}, ${statistics.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getStatistics', 'Failed to fetch statistics', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getStatistics', 'Failed to fetch statistics');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -83,10 +98,14 @@ class SABnzbdAPI {
|
||||
if(body['status'] != null && body['status']) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('pauseQueue', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('pauseQueue', 'Failed to pause queue', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('pauseQueue', 'Failed to pause queue');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -105,10 +124,14 @@ class SABnzbdAPI {
|
||||
if(body['status'] != null && body['status']) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('resumeQueue', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('resumeQueue', 'Failed to resume queue', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('resumeQueue', 'Failed to resume queue');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -127,10 +150,14 @@ class SABnzbdAPI {
|
||||
if(body['status'] != null && body['status']) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('pauseSingleJob', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('pauseSingleJob', 'Failed to pause job ($nzoId)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('pauseSingleJob', 'Failed to pause job ($nzoId)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -149,10 +176,14 @@ class SABnzbdAPI {
|
||||
if(body['status'] != null && body['status']) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('resumeSingleJob', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('resumeSingleJob', 'Failed to resume job ($nzoId)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('resumeSingleJob', 'Failed to resume job ($nzoId)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -171,10 +202,14 @@ class SABnzbdAPI {
|
||||
if(body['status'] != null && body['status']) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('deleteJob', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('deleteJob', 'Failed to delete job ($nzoId)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('deleteJob', 'Failed to delete job ($nzoId)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -193,10 +228,14 @@ class SABnzbdAPI {
|
||||
if(body['status'] != null && body['status']) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('renameJob', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('renameJob', 'Failed to rename job ($nzoId, $name)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('renameJob', 'Failed to rename job ($nzoId, $name)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -215,10 +254,14 @@ class SABnzbdAPI {
|
||||
if(body['status'] != null && body['status']) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('setJobPassword', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('setJobPassword', 'Failed to set job password ($nzoId, $password)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('setJobPassword', 'Failed to set job password ($nzoId, $password)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -237,10 +280,14 @@ class SABnzbdAPI {
|
||||
if(body['status'] || body['position'] != null) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('setJobPriority', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('setJobPriority', 'Failed to set job priority ($nzoId, $priority)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('setJobPriority', 'Failed to set job priority ($nzoId, $priority)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -259,10 +306,14 @@ class SABnzbdAPI {
|
||||
if(body['status'] != null && body['status']) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('deleteHistory', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('deleteHistory', 'Failed to delete history entry ($nzoId)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('deleteHistory', 'Failed to delete history entry ($nzoId)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -303,10 +354,14 @@ class SABnzbdAPI {
|
||||
queue,
|
||||
];
|
||||
}
|
||||
} else {
|
||||
logError('getStatusAndQueue', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getStatusAndQueue', 'Failed to fetch status and queue', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getStatusAndQueue', 'Failed to fetch status and queue');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -341,10 +396,14 @@ class SABnzbdAPI {
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
} else {
|
||||
logError('getHistory', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getHistory', 'Failed to fetch history', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getHistory', 'Failed to fetch history');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -365,10 +424,14 @@ class SABnzbdAPI {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logError('moveQueue', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('moveQueue', 'Failed to move queue entry ($nzoId, $index)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('moveQueue', 'Failed to move queue entry ($nzoId, $index)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -387,10 +450,14 @@ class SABnzbdAPI {
|
||||
if(body['status'] != null && body['status']) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('sortQueue', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('sortQueue', 'Failed to sort queue ($sort, $dir)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('sortQueue', 'Failed to sort queue ($sort, $dir)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -415,10 +482,14 @@ class SABnzbdAPI {
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
} else {
|
||||
logError('getCategories', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('getCategories', 'Failed to fetch categories', e);
|
||||
return null;
|
||||
}
|
||||
logWarning('getCategories', 'Failed to fetch categories');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -437,10 +508,14 @@ class SABnzbdAPI {
|
||||
if(body['status'] != null && body['status']) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('setCategory', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('setCategory', 'Failed to set category ($nzoId, $category)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('setCategory', 'Failed to set category ($nzoId, $category)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -459,10 +534,14 @@ class SABnzbdAPI {
|
||||
if(body['status'] != null && body['status']) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('setSpeedLimit', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('setSpeedLimit', 'Failed to set speed limit ($limit)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('setSpeedLimit', 'Failed to set speed limit ($limit)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -482,8 +561,14 @@ class SABnzbdAPI {
|
||||
if(body['status'] != null && body['status']) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('uploadURL', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch(e) {}
|
||||
} catch (e) {
|
||||
logError('uploadURL', 'Failed to upload NZB by URL ($url)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('uploadURL', 'Failed to upload NZB by URL ($url)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -501,10 +586,14 @@ class SABnzbdAPI {
|
||||
if(body['status'] != null && body['status']) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('uploadFile', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('uploadFile', 'Failed to upload nzb file ($name)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('uploadFile', 'Failed to upload nzb file ($name)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -523,10 +612,14 @@ class SABnzbdAPI {
|
||||
if(body['status'] != null && body['status']) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('setOnCompleteAction', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('setOnCompleteAction', 'Failed to set on-complete action ($action)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('setOnCompleteAction', 'Failed to set on-complete action ($action)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -545,10 +638,14 @@ class SABnzbdAPI {
|
||||
if(body['status'] != null && body['status']) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('clearHistory', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('clearHistory', 'Failed to clear history ($action)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('clearHistory', 'Failed to clear history ($action)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -567,10 +664,14 @@ class SABnzbdAPI {
|
||||
if(body['status'] != null && body['status']) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('retryFailedJob', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('retryFailedJob', 'Failed to retry job ($nzoId)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('retryFailedJob', 'Failed to retry job ($nzoId)');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -589,10 +690,14 @@ class SABnzbdAPI {
|
||||
if(body['status'] != null && body['status']) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logError('retryFailedJobPassword', '<GET> HTTP Status Code (${response.statusCode})', null);
|
||||
}
|
||||
} catch (e) {
|
||||
logError('retryFailedJobPassword', 'Failed to retry job with new password ($nzoId, $password)', e);
|
||||
return false;
|
||||
}
|
||||
logWarning('retryFailedJobPassword', 'Failed to retry job with new password ($nzoId, $password)');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/system/constants.dart';
|
||||
import 'package:lunasea/pages/pages.dart';
|
||||
import 'package:lunasea/configuration/configuration.dart';
|
||||
import 'package:lunasea/system/logger.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await Configuration.pullAndSanitizeValues();
|
||||
runApp(_BIOS());
|
||||
Logger.initialize();
|
||||
runZoned<Future<void>>(() async {
|
||||
runApp(_BIOS());
|
||||
}, onError: (Object error, StackTrace stack) {
|
||||
Logger.fatal(error, stack);
|
||||
});
|
||||
}
|
||||
|
||||
class _BIOS extends StatelessWidget {
|
||||
|
||||
@@ -49,9 +49,11 @@ class _LidarrState extends State<StatefulWidget> {
|
||||
];
|
||||
|
||||
void _navOnTap(int index) {
|
||||
setState(() {
|
||||
_currIndex = index;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_currIndex = index;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -169,9 +169,11 @@ class _LidarrArtistSearchDetailsState extends State<StatefulWidget> {
|
||||
trailing: Switch(
|
||||
value: _monitored,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_monitored = value;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_monitored = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -186,9 +188,11 @@ class _LidarrArtistSearchDetailsState extends State<StatefulWidget> {
|
||||
trailing: Switch(
|
||||
value: _albumFolders,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_albumFolders = value;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_albumFolders = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -205,7 +209,7 @@ class _LidarrArtistSearchDetailsState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await LidarrDialogs.showEditRootFolderPrompt(context, _rootFolders);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_rootFolder = _values[1];
|
||||
});
|
||||
@@ -225,7 +229,7 @@ class _LidarrArtistSearchDetailsState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await LidarrDialogs.showEditQualityProfilePrompt(context, _qualityProfiles);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_qualityProfile = _values[1];
|
||||
});
|
||||
@@ -245,7 +249,7 @@ class _LidarrArtistSearchDetailsState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await LidarrDialogs.showEditMetadataProfilePrompt(context, _metadataProfiles);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_metadataProfile = _values[1];
|
||||
});
|
||||
|
||||
@@ -60,12 +60,14 @@ class _CatalogueState extends State<StatefulWidget> with TickerProviderStateMixi
|
||||
_animationContoller = AnimationController(vsync: this, duration: kThemeAnimationDuration);
|
||||
_animationContoller?.forward();
|
||||
_searchController.addListener(() {
|
||||
setState(() {
|
||||
searchFilter = _searchController.text;
|
||||
_searchedEntries = _catalogueEntries.where(
|
||||
(entry) => searchFilter == 'null' || searchFilter == '' ? entry != null : entry.title.toLowerCase().contains(searchFilter.toLowerCase()),
|
||||
).toList();
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
searchFilter = _searchController.text;
|
||||
_searchedEntries = _catalogueEntries.where(
|
||||
(entry) => searchFilter == 'null' || searchFilter == '' ? entry != null : entry.title.toLowerCase().contains(searchFilter.toLowerCase()),
|
||||
).toList();
|
||||
});
|
||||
}
|
||||
});
|
||||
_scrollController.addListener(() {
|
||||
if(_scrollController?.position?.userScrollDirection == ScrollDirection.reverse) {
|
||||
@@ -124,9 +126,11 @@ class _CatalogueState extends State<StatefulWidget> with TickerProviderStateMixi
|
||||
child: Elements.getIcon(_hideUnmonitored ? Icons.visibility_off : Icons.visibility),
|
||||
tooltip: 'Hide/Unhide Unmonitored Artists',
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_hideUnmonitored = !_hideUnmonitored;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_hideUnmonitored = !_hideUnmonitored;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
scale: _animationContoller,
|
||||
@@ -253,9 +257,11 @@ class _CatalogueState extends State<StatefulWidget> with TickerProviderStateMixi
|
||||
tooltip: 'Toggle Monitored',
|
||||
onPressed: () async {
|
||||
if(await LidarrAPI.toggleArtistMonitored(entry.artistID, !entry.monitored)) {
|
||||
setState(() {
|
||||
entry.monitored = !entry.monitored;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
entry.monitored = !entry.monitored;
|
||||
});
|
||||
}
|
||||
Notifications.showSnackBar(
|
||||
_scaffoldKey,
|
||||
entry.monitored ? 'Monitoring ${entry.title}' : 'No longer monitoring ${entry.title}',
|
||||
@@ -355,9 +361,11 @@ class _CatalogueState extends State<StatefulWidget> with TickerProviderStateMixi
|
||||
if(result != null) {
|
||||
switch(result[0]) {
|
||||
case 'updated_artist': {
|
||||
setState(() {
|
||||
entry = result[1];
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
entry = result[1];
|
||||
});
|
||||
}
|
||||
Notifications.showSnackBar(_scaffoldKey, 'Updated ${entry.title}');
|
||||
break;
|
||||
}
|
||||
@@ -369,7 +377,7 @@ class _CatalogueState extends State<StatefulWidget> with TickerProviderStateMixi
|
||||
LidarrCatalogueEntry _entry = await LidarrAPI.getArtist(entry.artistID);
|
||||
_entry ??= _searchedEntries[index];
|
||||
if(mounted) {
|
||||
if(_searchedEntries[index]?.title == entry.title) {
|
||||
if(_searchedEntries[index]?.title == entry.title && mounted) {
|
||||
setState(() {
|
||||
_searchedEntries[index] = _entry;
|
||||
});
|
||||
|
||||
@@ -129,9 +129,11 @@ class _LidarrArtistDetailsState extends State<StatefulWidget> {
|
||||
IconButton(
|
||||
icon: Icon(_hideUnmonitored ? Icons.visibility_off : Icons.visibility),
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
_hideUnmonitored = !_hideUnmonitored;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_hideUnmonitored = !_hideUnmonitored;
|
||||
});
|
||||
}
|
||||
},
|
||||
tooltip: 'Hide/Unhide Unmonitored Content',
|
||||
),
|
||||
@@ -329,9 +331,11 @@ class _LidarrArtistDetailsState extends State<StatefulWidget> {
|
||||
tooltip: 'Toggle Monitored',
|
||||
onPressed: () async {
|
||||
if(await LidarrAPI.toggleAlbumMonitored(entry.albumID, !entry.monitored)) {
|
||||
setState(() {
|
||||
entry.monitored = !entry.monitored;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
entry.monitored = !entry.monitored;
|
||||
});
|
||||
}
|
||||
Notifications.showSnackBar(
|
||||
_scaffoldKey,
|
||||
entry.monitored ? 'Monitoring ${entry.title}' : 'No longer monitoring ${entry.title}',
|
||||
@@ -632,9 +636,11 @@ class _LidarrArtistDetailsState extends State<StatefulWidget> {
|
||||
if(result != null) {
|
||||
switch(result[0]) {
|
||||
case 'updated_artist': {
|
||||
setState(() {
|
||||
entry = result[1];
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
entry = result[1];
|
||||
});
|
||||
}
|
||||
Notifications.showSnackBar(_scaffoldKey, 'Updated ${entry.title}');
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -153,9 +153,11 @@ class _LidarrEditArtistState extends State<StatefulWidget> {
|
||||
trailing: Switch(
|
||||
value: _monitored,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_monitored = value;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_monitored = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -169,9 +171,11 @@ class _LidarrEditArtistState extends State<StatefulWidget> {
|
||||
trailing: Switch(
|
||||
value: _albumFolders,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_albumFolders = value;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_albumFolders = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -188,7 +192,7 @@ class _LidarrEditArtistState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SystemDialogs.showEditTextPrompt(context, 'Artist Path', prefill: _path);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_path = _values[1];
|
||||
});
|
||||
@@ -208,7 +212,7 @@ class _LidarrEditArtistState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await LidarrDialogs.showEditQualityProfilePrompt(context, _qualityProfiles);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_qualityProfile = _values[1];
|
||||
});
|
||||
@@ -228,7 +232,7 @@ class _LidarrEditArtistState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await LidarrDialogs.showEditMetadataProfilePrompt(context, _metadataProfiles);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_metadataProfile = _values[1];
|
||||
});
|
||||
|
||||
@@ -125,6 +125,10 @@ class _LidarrReleaseInfoState extends State<StatefulWidget> {
|
||||
onTap: () async {
|
||||
SystemDialogs.showTextPreviewPrompt(context, 'Release Title', entry.title);
|
||||
},
|
||||
trailing: IconButton(
|
||||
icon: Elements.getIcon(Icons.arrow_forward_ios),
|
||||
onPressed: null,
|
||||
),
|
||||
),
|
||||
margin: Elements.getCardMargin(),
|
||||
elevation: 4.0,
|
||||
|
||||
@@ -49,9 +49,11 @@ class _RadarrState extends State<StatefulWidget> {
|
||||
];
|
||||
|
||||
void _navOnTap(int index) {
|
||||
setState(() {
|
||||
_currIndex = index;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_currIndex = index;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -163,9 +163,11 @@ class _RadarrMovieSearchDetailsState extends State<StatefulWidget> {
|
||||
trailing: Switch(
|
||||
value: monitored,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
monitored = value;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
monitored = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -183,9 +185,11 @@ class _RadarrMovieSearchDetailsState extends State<StatefulWidget> {
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await RadarrDialogs.showEditRootFolderPrompt(context, _rootFolders);
|
||||
if(_values[0]) {
|
||||
_rootFolder = _values[1];
|
||||
setState(() {
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_rootFolder = _values[1];
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -202,7 +206,7 @@ class _RadarrMovieSearchDetailsState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await RadarrDialogs.showEditQualityProfilePrompt(context, _qualityProfiles);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_qualityProfile = _values[1];
|
||||
});
|
||||
@@ -222,7 +226,7 @@ class _RadarrMovieSearchDetailsState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await RadarrDialogs.showMinimumAvailabilityPrompt(context, Constants.radarrMinAvailability);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_minimumAvailability = _values[1];
|
||||
});
|
||||
|
||||
@@ -61,12 +61,14 @@ class _CatalogueState extends State<StatefulWidget> with TickerProviderStateMixi
|
||||
_animationContoller = AnimationController(vsync: this, duration: kThemeAnimationDuration);
|
||||
_animationContoller?.forward();
|
||||
_searchController.addListener(() {
|
||||
setState(() {
|
||||
searchFilter = _searchController.text;
|
||||
_searchedEntries = _catalogueEntries.where(
|
||||
(entry) => searchFilter == 'null' || searchFilter == '' ? entry != null : entry.title.toLowerCase().contains(searchFilter.toLowerCase()),
|
||||
).toList();
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
searchFilter = _searchController.text;
|
||||
_searchedEntries = _catalogueEntries.where(
|
||||
(entry) => searchFilter == 'null' || searchFilter == '' ? entry != null : entry.title.toLowerCase().contains(searchFilter.toLowerCase()),
|
||||
).toList();
|
||||
});
|
||||
}
|
||||
});
|
||||
_scrollController.addListener(() {
|
||||
if(_scrollController?.position?.userScrollDirection == ScrollDirection.reverse) {
|
||||
@@ -125,9 +127,11 @@ class _CatalogueState extends State<StatefulWidget> with TickerProviderStateMixi
|
||||
child: Elements.getIcon(_hideUnmonitored ? Icons.visibility_off : Icons.visibility),
|
||||
tooltip: 'Hide/Unhide Unmonitored Movies',
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_hideUnmonitored = !_hideUnmonitored;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_hideUnmonitored = !_hideUnmonitored;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
scale: _animationContoller,
|
||||
@@ -299,9 +303,11 @@ class _CatalogueState extends State<StatefulWidget> with TickerProviderStateMixi
|
||||
tooltip: 'Toggle Monitored',
|
||||
onPressed: () async {
|
||||
if(await RadarrAPI.toggleMovieMonitored(entry.movieID, !entry.monitored)) {
|
||||
setState(() {
|
||||
entry.monitored = !entry.monitored;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
entry.monitored = !entry.monitored;
|
||||
});
|
||||
}
|
||||
Notifications.showSnackBar(
|
||||
_scaffoldKey,
|
||||
entry.monitored ? 'Monitoring ${entry.title}' : 'No longer monitoring ${entry.title}',
|
||||
@@ -404,9 +410,11 @@ class _CatalogueState extends State<StatefulWidget> with TickerProviderStateMixi
|
||||
if(result != null) {
|
||||
switch(result[0]) {
|
||||
case 'updated_movie': {
|
||||
setState(() {
|
||||
entry = result[1];
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
entry = result[1];
|
||||
});
|
||||
}
|
||||
Notifications.showSnackBar(_scaffoldKey, 'Updated ${entry.title}');
|
||||
break;
|
||||
}
|
||||
@@ -418,7 +426,7 @@ class _CatalogueState extends State<StatefulWidget> with TickerProviderStateMixi
|
||||
RadarrCatalogueEntry _entry = await RadarrAPI.getMovie(entry.movieID);
|
||||
_entry ??= _searchedEntries[index];
|
||||
if(mounted) {
|
||||
if(_searchedEntries[index]?.title == entry.title) {
|
||||
if(_searchedEntries[index]?.title == entry.title && mounted) {
|
||||
setState(() {
|
||||
_searchedEntries[index] = _entry;
|
||||
});
|
||||
|
||||
@@ -136,9 +136,11 @@ class _RadarrEditMovieState extends State<StatefulWidget> {
|
||||
trailing: Switch(
|
||||
value: _monitored,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_monitored = value;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_monitored = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -152,9 +154,11 @@ class _RadarrEditMovieState extends State<StatefulWidget> {
|
||||
trailing: Switch(
|
||||
value: _static,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_static = value;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_static = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -171,7 +175,7 @@ class _RadarrEditMovieState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SystemDialogs.showEditTextPrompt(context, 'Movie Path', prefill: _path);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_path = _values[1];
|
||||
});
|
||||
@@ -191,7 +195,7 @@ class _RadarrEditMovieState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await RadarrDialogs.showEditQualityProfilePrompt(context, _qualityProfiles);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_qualityProfile = _values[1];
|
||||
});
|
||||
@@ -211,7 +215,7 @@ class _RadarrEditMovieState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await RadarrDialogs.showMinimumAvailabilityPrompt(context, Constants.radarrMinAvailability);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_minimumAvailability = _values[1];
|
||||
});
|
||||
|
||||
@@ -88,9 +88,11 @@ class _RadarrMovieDetailsState extends State<StatefulWidget> {
|
||||
}
|
||||
|
||||
void _needFetch() {
|
||||
setState(() {
|
||||
_loading = true;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_loading = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _refreshData() async {
|
||||
@@ -219,9 +221,11 @@ class _RadarrMovieDetailsState extends State<StatefulWidget> {
|
||||
if(result != null) {
|
||||
switch(result[0]) {
|
||||
case 'updated_movie': {
|
||||
setState(() {
|
||||
entry = result[1];
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
entry = result[1];
|
||||
});
|
||||
}
|
||||
Notifications.showSnackBar(_scaffoldKey, 'Updated ${entry.title}');
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -125,6 +125,10 @@ class _RadarrReleaseInfoState extends State<StatefulWidget> {
|
||||
onTap: () async {
|
||||
SystemDialogs.showTextPreviewPrompt(context, 'Release Title', entry.title);
|
||||
},
|
||||
trailing: IconButton(
|
||||
icon: Elements.getIcon(Icons.arrow_forward_ios),
|
||||
onPressed: null,
|
||||
),
|
||||
),
|
||||
margin: Elements.getCardMargin(),
|
||||
elevation: 4.0,
|
||||
|
||||
@@ -52,9 +52,11 @@ class _SABnzbdState extends State<StatefulWidget> {
|
||||
];
|
||||
|
||||
void _navOnTap(int index) {
|
||||
setState(() {
|
||||
_currIndex = index;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_currIndex = index;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _refreshStatus(SABnzbdStatusEntry entry) {
|
||||
|
||||
@@ -179,6 +179,10 @@ class _SABnzbdHistoryDetailsState extends State<StatefulWidget> {
|
||||
onTap: () async {
|
||||
await SystemDialogs.showTextPreviewPrompt(context, 'Storage Location', entry.storageLocation);
|
||||
},
|
||||
trailing: IconButton(
|
||||
icon: Elements.getIcon(Icons.arrow_forward_ios),
|
||||
onPressed: null,
|
||||
),
|
||||
),
|
||||
margin: Elements.getCardMargin(),
|
||||
elevation: 4.0,
|
||||
@@ -192,6 +196,10 @@ class _SABnzbdHistoryDetailsState extends State<StatefulWidget> {
|
||||
String data = stage['actions'].join(',\n').replaceAll('<br/>', '.\n');
|
||||
await SystemDialogs.showTextPreviewPrompt(context, '${stage['name']} Stage', data);
|
||||
},
|
||||
trailing: IconButton(
|
||||
icon: Elements.getIcon(Icons.arrow_forward_ios),
|
||||
onPressed: null,
|
||||
),
|
||||
),
|
||||
margin: Elements.getCardMargin(),
|
||||
elevation: 4.0,
|
||||
|
||||
@@ -143,9 +143,11 @@ class _SABnzbdHistoryState extends State<StatefulWidget> with TickerProviderStat
|
||||
)
|
||||
),
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
_hideCompleted = !_hideCompleted;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_hideCompleted = !_hideCompleted;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
scale: _animationContoller,
|
||||
|
||||
@@ -183,13 +183,13 @@ class _SABnzbdQueueState extends State<StatefulWidget> {
|
||||
),
|
||||
onPressed: () async {
|
||||
if(_paused) {
|
||||
if(await SABnzbdAPI.resumeQueue()) {
|
||||
if(await SABnzbdAPI.resumeQueue() && mounted) {
|
||||
setState(() {
|
||||
_paused = false;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if(await SABnzbdAPI.pauseQueue()) {
|
||||
if(await SABnzbdAPI.pauseQueue() && mounted) {
|
||||
setState(() {
|
||||
_paused = true;
|
||||
});
|
||||
@@ -240,7 +240,7 @@ class _SABnzbdQueueState extends State<StatefulWidget> {
|
||||
switch(values[1]) {
|
||||
case 'status': {
|
||||
if(entry.isPaused) {
|
||||
if(await SABnzbdAPI.resumeSingleJob(entry.nzoId)) {
|
||||
if(await SABnzbdAPI.resumeSingleJob(entry.nzoId) && mounted) {
|
||||
setState(() {
|
||||
entry.status = 'Downloading';
|
||||
});
|
||||
@@ -250,7 +250,7 @@ class _SABnzbdQueueState extends State<StatefulWidget> {
|
||||
Notifications.showSnackBar(scaffoldKey, 'Failed to resume job');
|
||||
}
|
||||
} else {
|
||||
if(await SABnzbdAPI.pauseSingleJob(entry.nzoId)) {
|
||||
if(await SABnzbdAPI.pauseSingleJob(entry.nzoId) && mounted) {
|
||||
setState(() {
|
||||
entry.status = 'Paused';
|
||||
});
|
||||
@@ -302,7 +302,7 @@ class _SABnzbdQueueState extends State<StatefulWidget> {
|
||||
case 'rename': {
|
||||
values = await SABnzbdDialogs.showRenameJobPrompt(context, entry.name);
|
||||
if(values[0]) {
|
||||
if(await SABnzbdAPI.renameJob(entry.nzoId, values[1])) {
|
||||
if(await SABnzbdAPI.renameJob(entry.nzoId, values[1]) && mounted) {
|
||||
setState(() {
|
||||
entry.name = values[1];
|
||||
});
|
||||
|
||||
@@ -42,9 +42,11 @@ class _SettingsState extends State<StatefulWidget> {
|
||||
];
|
||||
|
||||
void _navOnTap(int index) {
|
||||
setState(() {
|
||||
_currIndex = index;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_currIndex = index;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -37,9 +37,11 @@ class _LidarrState extends State<StatefulWidget> {
|
||||
}
|
||||
|
||||
void _refreshData() {
|
||||
setState(() {
|
||||
_lidarrValues = List.from(Values.lidarrValues);
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_lidarrValues = List.from(Values.lidarrValues);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildFloatingActionButton() {
|
||||
@@ -64,9 +66,11 @@ class _LidarrState extends State<StatefulWidget> {
|
||||
trailing: Switch(
|
||||
value: _lidarrValues[0],
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_lidarrValues[0] = value;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_lidarrValues[0] = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -83,7 +87,7 @@ class _LidarrState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SystemDialogs.showEditTextPrompt(context, 'Lidarr Host', prefill: _lidarrValues[1], showHostHint: true);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_lidarrValues[1] = _values[1];
|
||||
});
|
||||
@@ -103,7 +107,7 @@ class _LidarrState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SystemDialogs.showEditTextPrompt(context, 'Lidarr API Key', prefill: _lidarrValues[2]);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_lidarrValues[2] = _values[1];
|
||||
});
|
||||
|
||||
@@ -37,9 +37,11 @@ class _RadarrState extends State<StatefulWidget> {
|
||||
}
|
||||
|
||||
void _refreshData() {
|
||||
setState(() {
|
||||
_radarrValues = List.from(Values.radarrValues);
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_radarrValues = List.from(Values.radarrValues);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildFloatingActionButton() {
|
||||
@@ -64,9 +66,11 @@ class _RadarrState extends State<StatefulWidget> {
|
||||
trailing: Switch(
|
||||
value: _radarrValues[0],
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_radarrValues[0] = value;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_radarrValues[0] = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -83,7 +87,7 @@ class _RadarrState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SystemDialogs.showEditTextPrompt(context, 'Radarr Host', prefill: _radarrValues[1], showHostHint: true);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_radarrValues[1] = _values[1];
|
||||
});
|
||||
@@ -103,7 +107,7 @@ class _RadarrState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SystemDialogs.showEditTextPrompt(context, 'Radarr API Key', prefill: _radarrValues[2]);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_radarrValues[2] = _values[1];
|
||||
});
|
||||
|
||||
@@ -37,9 +37,11 @@ class _SonarrState extends State<StatefulWidget> {
|
||||
}
|
||||
|
||||
void _refreshData() {
|
||||
setState(() {
|
||||
_sonarrValues = List.from(Values.sonarrValues);
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_sonarrValues = List.from(Values.sonarrValues);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildFloatingActionButton() {
|
||||
@@ -64,9 +66,11 @@ class _SonarrState extends State<StatefulWidget> {
|
||||
trailing: Switch(
|
||||
value: _sonarrValues[0],
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_sonarrValues[0] = value;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_sonarrValues[0] = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -83,7 +87,7 @@ class _SonarrState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SystemDialogs.showEditTextPrompt(context, 'Sonarr Host', prefill: _sonarrValues[1], showHostHint: true);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_sonarrValues[1] = _values[1];
|
||||
});
|
||||
@@ -103,7 +107,7 @@ class _SonarrState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SystemDialogs.showEditTextPrompt(context, 'Sonarr API Key', prefill: _sonarrValues[2]);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_sonarrValues[2] = _values[1];
|
||||
});
|
||||
|
||||
@@ -37,9 +37,11 @@ class _SABnzbdState extends State<StatefulWidget> {
|
||||
}
|
||||
|
||||
void _refreshData() {
|
||||
setState(() {
|
||||
_sabnzbdValues = List.from(Values.sabnzbdValues);
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_sabnzbdValues = List.from(Values.sabnzbdValues);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildFloatingActionButton() {
|
||||
@@ -64,9 +66,11 @@ class _SABnzbdState extends State<StatefulWidget> {
|
||||
trailing: Switch(
|
||||
value: _sabnzbdValues[0],
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_sabnzbdValues[0] = value;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_sabnzbdValues[0] = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -83,7 +87,7 @@ class _SABnzbdState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SystemDialogs.showEditTextPrompt(context, 'SABnzbd Host', prefill: _sabnzbdValues[1], showHostHint: true);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_sabnzbdValues[1] = _values[1];
|
||||
});
|
||||
@@ -103,7 +107,7 @@ class _SABnzbdState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SystemDialogs.showEditTextPrompt(context, 'SABnzbd API Key', prefill: _sabnzbdValues[2]);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_sabnzbdValues[2] = _values[1];
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ class _GeneralState extends State<StatefulWidget> {
|
||||
final List<String> _titles = [
|
||||
'Profile',
|
||||
'Configuration',
|
||||
'Logs',
|
||||
'LunaSea',
|
||||
];
|
||||
|
||||
@@ -33,6 +34,7 @@ class _GeneralState extends State<StatefulWidget> {
|
||||
children: <Widget>[
|
||||
Profile(),
|
||||
BackupRestore(),
|
||||
Logs(),
|
||||
LunaSea(),
|
||||
],
|
||||
),
|
||||
|
||||
106
lib/pages/settings/subpages/general/tabs/logs.dart
Normal file
106
lib/pages/settings/subpages/general/tabs/logs.dart
Normal file
@@ -0,0 +1,106 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/pages/settings/subpages/general/tabs/logs/type.dart';
|
||||
import 'package:lunasea/system/logger.dart';
|
||||
import 'package:lunasea/system/ui.dart';
|
||||
|
||||
class Logs extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _LogsWidget();
|
||||
}
|
||||
}
|
||||
|
||||
class _LogsWidget extends StatefulWidget {
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _LogsState();
|
||||
}
|
||||
}
|
||||
|
||||
class _LogsState extends State<StatefulWidget> {
|
||||
final _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
body: _logSettings(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _logSettings() {
|
||||
return Scrollbar(
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
Card(
|
||||
child: ListTile(
|
||||
title: Elements.getTitle('View Logs'),
|
||||
subtitle: Elements.getSubtitle('View all recorded logs'),
|
||||
trailing: IconButton(
|
||||
icon: Elements.getIcon(Icons.developer_mode),
|
||||
onPressed: null,
|
||||
),
|
||||
onTap: () async {
|
||||
await _viewLogs();
|
||||
},
|
||||
),
|
||||
margin: Elements.getCardMargin(),
|
||||
elevation: 4.0,
|
||||
),
|
||||
Card(
|
||||
child: ListTile(
|
||||
title: Elements.getTitle('Export Logs'),
|
||||
subtitle: Elements.getSubtitle('Export all recorded logs'),
|
||||
trailing: IconButton(
|
||||
icon: Elements.getIcon(Icons.file_download),
|
||||
onPressed: null,
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SystemDialogs.showExportLogsPrompt(context);
|
||||
if(_values[0]) {
|
||||
Logger.exportLogs();
|
||||
Notifications.showSnackBar(_scaffoldKey, 'Exported recorded logs to the filesystem');
|
||||
}
|
||||
},
|
||||
),
|
||||
margin: Elements.getCardMargin(),
|
||||
elevation: 4.0,
|
||||
),
|
||||
Card(
|
||||
child: ListTile(
|
||||
title: Elements.getTitle('Clear Logs'),
|
||||
subtitle: Elements.getSubtitle('Clear all recorded logs'),
|
||||
trailing: IconButton(
|
||||
icon: Elements.getIcon(Icons.delete),
|
||||
onPressed: null,
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SystemDialogs.showClearLogsPrompt(context);
|
||||
if(_values[0]) {
|
||||
Logger.clearLogs();
|
||||
Notifications.showSnackBar(_scaffoldKey, 'All recorded logs have been cleared');
|
||||
}
|
||||
},
|
||||
),
|
||||
margin: Elements.getCardMargin(),
|
||||
elevation: 4.0,
|
||||
),
|
||||
],
|
||||
padding: Elements.getListViewPadding(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _viewLogs() async {
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => TypeLogs(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
153
lib/pages/settings/subpages/general/tabs/logs/details.dart
Normal file
153
lib/pages/settings/subpages/general/tabs/logs/details.dart
Normal file
@@ -0,0 +1,153 @@
|
||||
import 'package:f_logs/f_logs.dart' as FLog;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/system/ui.dart';
|
||||
|
||||
class LogDetails extends StatelessWidget {
|
||||
final FLog.Log log;
|
||||
|
||||
LogDetails({
|
||||
Key key,
|
||||
@required this.log,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _LogDetailsWidget(log: log);
|
||||
}
|
||||
}
|
||||
|
||||
class _LogDetailsWidget extends StatefulWidget {
|
||||
final FLog.Log log;
|
||||
|
||||
_LogDetailsWidget({
|
||||
Key key,
|
||||
@required this.log,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _LogDetailsState(log: log);
|
||||
}
|
||||
}
|
||||
|
||||
class _LogDetailsState extends State<StatefulWidget> {
|
||||
final FLog.Log log;
|
||||
final _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
_LogDetailsState({
|
||||
Key key,
|
||||
@required this.log,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: Navigation.getAppBar('Log Details', context),
|
||||
body: _buildDetails(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDetails() {
|
||||
return Scrollbar(
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
_buildPackage(),
|
||||
_buildMethod(),
|
||||
_buildTimestamp(),
|
||||
_buildMessage(),
|
||||
if(log.exception != 'null') _buildException(),
|
||||
if(log.stacktrace != 'null') _buildStackTrace(),
|
||||
],
|
||||
padding: Elements.getListViewPadding(extraBottom: true),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPackage() {
|
||||
return Card(
|
||||
child: ListTile(
|
||||
title: Elements.getTitle('Class'),
|
||||
subtitle: Elements.getSubtitle(log.className, preventOverflow: true),
|
||||
),
|
||||
margin: Elements.getCardMargin(),
|
||||
elevation: 4.0,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMethod() {
|
||||
return Card(
|
||||
child: ListTile(
|
||||
title: Elements.getTitle('Method'),
|
||||
subtitle: Elements.getSubtitle(log.methodName, preventOverflow: true),
|
||||
),
|
||||
margin: Elements.getCardMargin(),
|
||||
elevation: 4.0,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTimestamp() {
|
||||
return Card(
|
||||
child: ListTile(
|
||||
title: Elements.getTitle('Timestamp'),
|
||||
subtitle: Elements.getSubtitle(log.timestamp, preventOverflow: true),
|
||||
),
|
||||
margin: Elements.getCardMargin(),
|
||||
elevation: 4.0,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMessage() {
|
||||
return Card(
|
||||
child: ListTile(
|
||||
title: Elements.getTitle('Message'),
|
||||
subtitle: Elements.getSubtitle(log.text, preventOverflow: true),
|
||||
onTap: () async {
|
||||
SystemDialogs.showTextPreviewPrompt(context, 'Message', log.text);
|
||||
},
|
||||
trailing: IconButton(
|
||||
icon: Elements.getIcon(Icons.arrow_forward_ios),
|
||||
onPressed: null,
|
||||
),
|
||||
),
|
||||
margin: Elements.getCardMargin(),
|
||||
elevation: 4.0,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildException() {
|
||||
return Card(
|
||||
child: ListTile(
|
||||
title: Elements.getTitle('Exception'),
|
||||
subtitle: Elements.getSubtitle(log.exception, preventOverflow: true),
|
||||
onTap: () async {
|
||||
SystemDialogs.showTextPreviewPrompt(context, 'Exception', log.exception, alignLeft: true);
|
||||
},
|
||||
trailing: IconButton(
|
||||
icon: Elements.getIcon(Icons.arrow_forward_ios),
|
||||
onPressed: null,
|
||||
),
|
||||
),
|
||||
margin: Elements.getCardMargin(),
|
||||
elevation: 4.0,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStackTrace() {
|
||||
return Card(
|
||||
child: ListTile(
|
||||
title: Elements.getTitle('Stack Trace'),
|
||||
subtitle: Elements.getSubtitle(log.stacktrace, preventOverflow: true),
|
||||
onTap: () async {
|
||||
SystemDialogs.showTextPreviewPrompt(context, 'Stack Trace', log.stacktrace, alignLeft: true);
|
||||
},
|
||||
trailing: IconButton(
|
||||
icon: Elements.getIcon(Icons.arrow_forward_ios),
|
||||
onPressed: null,
|
||||
),
|
||||
),
|
||||
margin: Elements.getCardMargin(),
|
||||
elevation: 4.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
109
lib/pages/settings/subpages/general/tabs/logs/type.dart
Normal file
109
lib/pages/settings/subpages/general/tabs/logs/type.dart
Normal file
@@ -0,0 +1,109 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/pages/settings/subpages/general/tabs/logs/view.dart';
|
||||
import 'package:lunasea/system/ui.dart';
|
||||
|
||||
class TypeLogs extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _TypeLogsWidget();
|
||||
}
|
||||
}
|
||||
|
||||
class _TypeLogsWidget extends StatefulWidget {
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _TypeLogsState();
|
||||
}
|
||||
}
|
||||
|
||||
class _TypeLogsState extends State<StatefulWidget> {
|
||||
final _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: Navigation.getAppBar('Log Types', context),
|
||||
body: _buildList(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildList() {
|
||||
return Scrollbar(
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
Card(
|
||||
child: ListTile(
|
||||
title: Elements.getTitle('All Logs'),
|
||||
subtitle: Elements.getSubtitle('View logs of all types'),
|
||||
trailing: IconButton(
|
||||
icon: Elements.getIcon(Icons.developer_mode),
|
||||
onPressed: null,
|
||||
),
|
||||
onTap: () async {
|
||||
await _viewLogs('All');
|
||||
},
|
||||
),
|
||||
elevation: 4.0,
|
||||
margin: Elements.getCardMargin(),
|
||||
),
|
||||
Elements.getDivider(),
|
||||
Card(
|
||||
child: ListTile(
|
||||
title: Elements.getTitle('Warning'),
|
||||
subtitle: Elements.getSubtitle('View warning logs'),
|
||||
trailing: IconButton(
|
||||
icon: Elements.getIcon(Icons.warning),
|
||||
onPressed: null,
|
||||
),
|
||||
onTap: () async {
|
||||
await _viewLogs('Warning');
|
||||
},
|
||||
),
|
||||
elevation: 4.0,
|
||||
margin: Elements.getCardMargin(),
|
||||
),
|
||||
Card(
|
||||
child: ListTile(
|
||||
title: Elements.getTitle('Error'),
|
||||
subtitle: Elements.getSubtitle('View error logs'),
|
||||
trailing: IconButton(
|
||||
icon: Elements.getIcon(Icons.report),
|
||||
onPressed: null,
|
||||
),
|
||||
onTap: () async {
|
||||
await _viewLogs('Error');
|
||||
},
|
||||
),
|
||||
elevation: 4.0,
|
||||
margin: Elements.getCardMargin(),
|
||||
),
|
||||
Card(
|
||||
child: ListTile(
|
||||
title: Elements.getTitle('Fatal'),
|
||||
subtitle: Elements.getSubtitle('View fatal logs'),
|
||||
trailing: IconButton(
|
||||
icon: Elements.getIcon(Icons.new_releases),
|
||||
onPressed: null,
|
||||
),
|
||||
onTap: () async {
|
||||
await _viewLogs('Fatal');
|
||||
},
|
||||
),
|
||||
elevation: 4.0,
|
||||
margin: Elements.getCardMargin(),
|
||||
),
|
||||
],
|
||||
padding: Elements.getListViewPadding(extraBottom: true),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _viewLogs(String type) async {
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ViewLogs(type: type),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
189
lib/pages/settings/subpages/general/tabs/logs/view.dart
Normal file
189
lib/pages/settings/subpages/general/tabs/logs/view.dart
Normal file
@@ -0,0 +1,189 @@
|
||||
import 'package:f_logs/f_logs.dart' as FLog;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/pages/settings/subpages/general/tabs/logs/details.dart';
|
||||
import 'package:lunasea/system/constants.dart';
|
||||
import 'package:lunasea/system/ui.dart';
|
||||
|
||||
class ViewLogs extends StatelessWidget {
|
||||
final String type;
|
||||
|
||||
ViewLogs({
|
||||
Key key,
|
||||
@required this.type,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _ViewLogsWidget(type: type);
|
||||
}
|
||||
}
|
||||
|
||||
class _ViewLogsWidget extends StatefulWidget {
|
||||
final String type;
|
||||
|
||||
_ViewLogsWidget({
|
||||
Key key,
|
||||
@required this.type,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _ViewLogsState(type: type);
|
||||
}
|
||||
}
|
||||
|
||||
class _ViewLogsState extends State<StatefulWidget> {
|
||||
final String type;
|
||||
final _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
bool _loading = true;
|
||||
List<FLog.Log> _logs;
|
||||
List<String> levels;
|
||||
|
||||
_ViewLogsState({
|
||||
Key key,
|
||||
@required this.type,
|
||||
});
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_setLogLevel();
|
||||
_fetchLogs();
|
||||
}
|
||||
|
||||
void _setLogLevel() {
|
||||
switch(type) {
|
||||
case 'All': { levels = []; break; }
|
||||
case 'Warning': { levels = [FLog.LogLevel.WARNING.toString()]; break; }
|
||||
case 'Error': { levels = [FLog.LogLevel.ERROR.toString()]; break; }
|
||||
case 'Fatal': { levels = [FLog.LogLevel.FATAL.toString()]; break; }
|
||||
default: { levels = null; break; }
|
||||
}
|
||||
}
|
||||
|
||||
TextSpan _getLogLevel(FLog.LogLevel level) {
|
||||
switch(level.toString()) {
|
||||
case 'LogLevel.WARNING': {
|
||||
return TextSpan(
|
||||
text: '\nWARNING',
|
||||
style: TextStyle(
|
||||
color: Colors.orange,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
);
|
||||
}
|
||||
case 'LogLevel.ERROR': {
|
||||
return TextSpan(
|
||||
text: '\nERROR',
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
);
|
||||
}
|
||||
case 'LogLevel.FATAL': {
|
||||
return TextSpan(
|
||||
text: '\nFATAL',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return TextSpan(
|
||||
text: '\nUNKNOWN',
|
||||
style: TextStyle(
|
||||
color: Colors.blueGrey,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _fetchLogs() async {
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_loading = true;
|
||||
});
|
||||
}
|
||||
_logs = await FLog.FLog.getAllLogsByFilter(
|
||||
logLevels: levels,
|
||||
);
|
||||
_logs.sort((a, b) => b.timeInMillis.compareTo(a.timeInMillis));
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: Navigation.getAppBar('$type Logs', context),
|
||||
body: _loading ?
|
||||
Notifications.centeredMessage('Loading...') :
|
||||
_logs == null ?
|
||||
Notifications.centeredMessage('An Error has Occurred') :
|
||||
_logs.length == 0 ?
|
||||
Notifications.centeredMessage('No Logs Found') :
|
||||
_buildLogList(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLogList() {
|
||||
return Scrollbar(
|
||||
child: ListView.builder(
|
||||
itemCount: _logs.length,
|
||||
itemBuilder: (context, index) {
|
||||
return _buildEntry(_logs[index]);
|
||||
},
|
||||
padding: Elements.getListViewPadding(extraBottom: true),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEntry(FLog.Log log) {
|
||||
return Card(
|
||||
child: ListTile(
|
||||
title: Elements.getTitle(log.text),
|
||||
subtitle: RichText(
|
||||
text: TextSpan(
|
||||
style: TextStyle(
|
||||
color: Colors.white70,
|
||||
letterSpacing: Constants.LETTER_SPACING,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '${log.timestamp}',
|
||||
),
|
||||
_getLogLevel(log.logLevel),
|
||||
],
|
||||
),
|
||||
|
||||
),
|
||||
trailing: IconButton(
|
||||
icon: Elements.getIcon(Icons.arrow_forward_ios),
|
||||
onPressed: null,
|
||||
),
|
||||
onTap: () async {
|
||||
await _logDetails(log);
|
||||
},
|
||||
contentPadding: Elements.getContentPadding(),
|
||||
),
|
||||
margin: Elements.getCardMargin(),
|
||||
elevation: 4.0,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _logDetails(FLog.Log log) async {
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => LogDetails(log: log),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -39,10 +39,12 @@ class _LunaSeaState extends State<StatefulWidget> {
|
||||
|
||||
void _fetchVersion() async {
|
||||
PackageInfo info = await PackageInfo.fromPlatform();
|
||||
setState(() {
|
||||
_version = info.version;
|
||||
_buildNumber = info.buildNumber;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_version = info.version;
|
||||
_buildNumber = info.buildNumber;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Widget _systemLinks() {
|
||||
|
||||
@@ -29,10 +29,12 @@ class _ProfileState extends State<StatefulWidget> {
|
||||
}
|
||||
|
||||
void _refreshData() {
|
||||
setState(() {
|
||||
_enabled = Profiles.enabledProfile;
|
||||
_profiles = Profiles.profileList;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_enabled = Profiles.enabledProfile;
|
||||
_profiles = Profiles.profileList;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export 'package:lunasea/pages/settings/subpages/general/tabs/backup_restore.dart';
|
||||
export 'package:lunasea/pages/settings/subpages/general/tabs/logs.dart';
|
||||
export 'package:lunasea/pages/settings/subpages/general/tabs/lunasea.dart';
|
||||
export 'package:lunasea/pages/settings/subpages/general/tabs/profile.dart';
|
||||
|
||||
@@ -53,9 +53,11 @@ class _SonarrState extends State<StatefulWidget> {
|
||||
];
|
||||
|
||||
void _navOnTap(int index) {
|
||||
setState(() {
|
||||
_currIndex = index;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_currIndex = index;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -165,9 +165,11 @@ class _SonarrSeriesSearchDetailsState extends State<StatefulWidget> {
|
||||
trailing: Switch(
|
||||
value: monitored,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
monitored = value;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
monitored = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -181,9 +183,11 @@ class _SonarrSeriesSearchDetailsState extends State<StatefulWidget> {
|
||||
trailing: Switch(
|
||||
value: seasonFolders,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
seasonFolders = value;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
seasonFolders = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -200,7 +204,7 @@ class _SonarrSeriesSearchDetailsState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SonarrDialogs.showEditRootFolderPrompt(context, _rootFolders);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_rootFolder = _values[1];
|
||||
});
|
||||
@@ -220,7 +224,7 @@ class _SonarrSeriesSearchDetailsState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SonarrDialogs.showEditQualityProfilePrompt(context, _qualityProfiles);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_qualityProfile = _values[1];
|
||||
});
|
||||
@@ -240,7 +244,7 @@ class _SonarrSeriesSearchDetailsState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SonarrDialogs.showEditSeriesTypePrompt(context);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_seriesType = _values[1];
|
||||
});
|
||||
|
||||
@@ -60,12 +60,14 @@ class _CatalogueState extends State<StatefulWidget> with TickerProviderStateMixi
|
||||
_animationContoller = AnimationController(vsync: this, duration: kThemeAnimationDuration);
|
||||
_animationContoller?.forward();
|
||||
_searchController.addListener(() {
|
||||
setState(() {
|
||||
searchFilter = _searchController.text;
|
||||
_searchedEntries = _catalogueEntries.where(
|
||||
(entry) => searchFilter == 'null' || searchFilter == '' ? entry != null : entry.title.toLowerCase().contains(searchFilter.toLowerCase()),
|
||||
).toList();
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
searchFilter = _searchController.text;
|
||||
_searchedEntries = _catalogueEntries.where(
|
||||
(entry) => searchFilter == 'null' || searchFilter == '' ? entry != null : entry.title.toLowerCase().contains(searchFilter.toLowerCase()),
|
||||
).toList();
|
||||
});
|
||||
}
|
||||
});
|
||||
_scrollController.addListener(() {
|
||||
if(_scrollController?.position?.userScrollDirection == ScrollDirection.reverse) {
|
||||
@@ -124,9 +126,11 @@ class _CatalogueState extends State<StatefulWidget> with TickerProviderStateMixi
|
||||
child: Elements.getIcon(_hideUnmonitored ? Icons.visibility_off : Icons.visibility),
|
||||
tooltip: 'Hide/Unhide Unmonitored Series',
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_hideUnmonitored = !_hideUnmonitored;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_hideUnmonitored = !_hideUnmonitored;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
scale: _animationContoller,
|
||||
@@ -252,7 +256,7 @@ class _CatalogueState extends State<StatefulWidget> with TickerProviderStateMixi
|
||||
),
|
||||
tooltip: 'Toggle Monitored',
|
||||
onPressed: () async {
|
||||
if(await SonarrAPI.toggleSeriesMonitored(entry.seriesID, !entry.monitored)) {
|
||||
if(await SonarrAPI.toggleSeriesMonitored(entry.seriesID, !entry.monitored) && mounted) {
|
||||
setState(() {
|
||||
entry.monitored = !entry.monitored;
|
||||
});
|
||||
@@ -355,9 +359,11 @@ class _CatalogueState extends State<StatefulWidget> with TickerProviderStateMixi
|
||||
if(result != null) {
|
||||
switch(result[0]) {
|
||||
case 'updated_series': {
|
||||
setState(() {
|
||||
entry = result[1];
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
entry = result[1];
|
||||
});
|
||||
}
|
||||
Notifications.showSnackBar(_scaffoldKey, 'Updated ${entry.title}');
|
||||
break;
|
||||
}
|
||||
@@ -369,7 +375,7 @@ class _CatalogueState extends State<StatefulWidget> with TickerProviderStateMixi
|
||||
SonarrCatalogueEntry _entry = await SonarrAPI.getSeries(entry.seriesID);
|
||||
_entry ??= _searchedEntries[index];
|
||||
if(mounted) {
|
||||
if(_searchedEntries[index]?.title == entry.title) {
|
||||
if(_searchedEntries[index]?.title == entry.title && mounted) {
|
||||
setState(() {
|
||||
_searchedEntries[index] = _entry;
|
||||
});
|
||||
|
||||
@@ -132,9 +132,11 @@ class _SonarrEditSeriesState extends State<StatefulWidget> {
|
||||
trailing: Switch(
|
||||
value: _monitored,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_monitored = value;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_monitored = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -148,9 +150,11 @@ class _SonarrEditSeriesState extends State<StatefulWidget> {
|
||||
trailing: Switch(
|
||||
value: _seasonFolder,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_seasonFolder = value;
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_seasonFolder = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -167,7 +171,7 @@ class _SonarrEditSeriesState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SystemDialogs.showEditTextPrompt(context, 'Series Path', prefill: _path);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_path = _values[1];
|
||||
});
|
||||
@@ -187,7 +191,7 @@ class _SonarrEditSeriesState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SonarrDialogs.showEditQualityProfilePrompt(context, _qualityProfiles);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_qualityProfile = _values[1];
|
||||
});
|
||||
@@ -207,7 +211,7 @@ class _SonarrEditSeriesState extends State<StatefulWidget> {
|
||||
),
|
||||
onTap: () async {
|
||||
List<dynamic> _values = await SonarrDialogs.showEditSeriesTypePrompt(context);
|
||||
if(_values[0]) {
|
||||
if(_values[0] && mounted) {
|
||||
setState(() {
|
||||
_seriesType = _values[1];
|
||||
});
|
||||
|
||||
@@ -125,6 +125,10 @@ class _SonarrReleaseInfoState extends State<StatefulWidget> {
|
||||
onTap: () async {
|
||||
SystemDialogs.showTextPreviewPrompt(context, 'Release Title', entry.title);
|
||||
},
|
||||
trailing: IconButton(
|
||||
icon: Elements.getIcon(Icons.arrow_forward_ios),
|
||||
onPressed: null,
|
||||
),
|
||||
),
|
||||
margin: Elements.getCardMargin(),
|
||||
elevation: 4.0,
|
||||
|
||||
@@ -138,7 +138,9 @@ class _SonarrSeasonDetailsState extends State<StatefulWidget> {
|
||||
}
|
||||
|
||||
void _selectedCallback() {
|
||||
setState(() {});
|
||||
if(mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<dynamic>> _searchEpisodes() async {
|
||||
@@ -239,7 +241,9 @@ class _SonarrSeasonDetailsState extends State<StatefulWidget> {
|
||||
}
|
||||
card.entry.isSelected = flag;
|
||||
}
|
||||
setState(() {});
|
||||
if(mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
onLongPress: () async {
|
||||
List<dynamic> values = await SonarrDialogs.showSearchSeasonPrompt(context, season);
|
||||
|
||||
@@ -415,14 +415,14 @@ class _SonarrShowDetailsState extends State<StatefulWidget> {
|
||||
seasonNumber = -1;
|
||||
episodeCount = entry.episodeCount;
|
||||
availableEpisodeCount = entry.episodeFileCount;
|
||||
percentage = (entry.episodeCount == 0 || entry.episodeFileCount == null || entry.episodeCount == null) ? 0 : ((entry.episodeFileCount/entry.episodeCount)*100).round();
|
||||
percentage = (episodeCount == null || availableEpisodeCount == null || episodeCount == 0) ? 0 : ((availableEpisodeCount/episodeCount)*100).round();
|
||||
isMonitored = entry.monitored;
|
||||
} else {
|
||||
season = entry.seasonData[entry.seasonData.length-index];
|
||||
seasonNumber = season['seasonNumber'];
|
||||
episodeCount = season['statistics']['totalEpisodeCount'];
|
||||
availableEpisodeCount = season['statistics']['episodeFileCount'];
|
||||
percentage = (episodeCount == 0 || availableEpisodeCount == null || episodeCount == null) ? 0 : ((availableEpisodeCount/episodeCount)*100).round();
|
||||
percentage = (episodeCount == null || availableEpisodeCount == null || episodeCount == 0) ? 0 : ((availableEpisodeCount/episodeCount)*100).round();
|
||||
isMonitored = season['monitored'];
|
||||
}
|
||||
return Card(
|
||||
@@ -468,7 +468,7 @@ class _SonarrShowDetailsState extends State<StatefulWidget> {
|
||||
onPressed: seasonNumber >= 0 ?
|
||||
() async {
|
||||
String snackMsg = seasonNumber == 0 ? 'specials' : 'season $seasonNumber';
|
||||
if(await SonarrAPI.toggleSeasonMonitored(entry.seriesID, seasonNumber, !season['monitored'])) {
|
||||
if(await SonarrAPI.toggleSeasonMonitored(entry.seriesID, seasonNumber, !season['monitored']) && mounted) {
|
||||
setState(() {
|
||||
season['monitored'] = !season['monitored'];
|
||||
});
|
||||
@@ -626,9 +626,11 @@ class _SonarrShowDetailsState extends State<StatefulWidget> {
|
||||
if(result != null) {
|
||||
switch(result[0]) {
|
||||
case 'updated_series': {
|
||||
setState(() {
|
||||
entry = result[1];
|
||||
});
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
entry = result[1];
|
||||
});
|
||||
}
|
||||
Notifications.showSnackBar(_scaffoldKey, 'Updated ${entry.title}');
|
||||
break;
|
||||
}
|
||||
|
||||
62
lib/system/logger.dart
Normal file
62
lib/system/logger.dart
Normal file
@@ -0,0 +1,62 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:stack_trace/stack_trace.dart';
|
||||
import 'package:f_logs/f_logs.dart';
|
||||
|
||||
class Logger {
|
||||
Logger._();
|
||||
|
||||
static void initialize() {
|
||||
LogsConfig config = FLog.getDefaultConfigurations()
|
||||
..formatType = FormatType.FORMAT_SQUARE
|
||||
..timestampFormat = 'MMMM dd, y - hh:mm:ss a';
|
||||
FLog.applyConfigurations(config);
|
||||
FlutterError.onError = (FlutterErrorDetails details) async {
|
||||
bool inDebugMode = false;
|
||||
assert(inDebugMode = true);
|
||||
if (inDebugMode) {
|
||||
FlutterError.dumpErrorToConsole(details);
|
||||
}
|
||||
Zone.current.handleUncaughtError(details.exception, details.stack);
|
||||
};
|
||||
}
|
||||
|
||||
static void warning(String className, String methodName, String text, {DataLogType type = DataLogType.DEFAULT}) {
|
||||
FLog.warning(
|
||||
className: className,
|
||||
methodName: methodName,
|
||||
text: text,
|
||||
dataLogType: type.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
static void error(String className, String methodName, String text, Object error, StackTrace trace, {DataLogType type = DataLogType.DEFAULT}) {
|
||||
FLog.error(
|
||||
className: className,
|
||||
methodName: methodName,
|
||||
text: text,
|
||||
exception: Exception(error.toString()),
|
||||
stacktrace: trace,
|
||||
dataLogType: type.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
static void fatal(Object error, StackTrace trace, {DataLogType type = DataLogType.DEFAULT}) {
|
||||
FLog.fatal(
|
||||
className: Trace.from(trace).frames[1].uri.toString() ?? 'Unknown',
|
||||
methodName: Trace.from(trace).frames[1].member.toString() ?? 'Unknown',
|
||||
text: error.toString(),
|
||||
exception: Exception(error.toString()),
|
||||
stacktrace: trace,
|
||||
dataLogType: type.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
static void exportLogs() {
|
||||
FLog.exportLogs();
|
||||
}
|
||||
|
||||
static void clearLogs() {
|
||||
FLog.clearLogs();
|
||||
}
|
||||
}
|
||||
@@ -827,7 +827,7 @@ class SystemDialogs {
|
||||
return [flag, textController.text];
|
||||
}
|
||||
|
||||
static Future<void> showTextPreviewPrompt(BuildContext context, String title, String text) async {
|
||||
static Future<void> showTextPreviewPrompt(BuildContext context, String title, String text, {bool alignLeft = false}) async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
@@ -860,7 +860,7 @@ class SystemDialogs {
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
textAlign: alignLeft ? TextAlign.left : TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -870,4 +870,130 @@ class SystemDialogs {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showClearLogsPrompt(BuildContext context) async {
|
||||
bool flag = false;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Clear Logs',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Clear',
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: Text(
|
||||
'Are you sure you want to clear all recorded logs?\n\nLogs can be useful for bug reports and debugging.',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showExportLogsPrompt(BuildContext context) async {
|
||||
bool flag = false;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Export Logs',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Export',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
letterSpacing: Constants.LETTER_SPACING,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
children: <TextSpan>[
|
||||
TextSpan(
|
||||
text: 'Are you sure you want to export all recorded logs to the filesystem?\n\nThe exported logs can be found in\n',
|
||||
),
|
||||
TextSpan(
|
||||
text: 'On My <Device>/LunaSea/FLogs',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: '.',
|
||||
),
|
||||
]
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
return [flag];
|
||||
}
|
||||
}
|
||||
|
||||
37
pubspec.lock
37
pubspec.lock
@@ -99,6 +99,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
f_logs:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: f_logs
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
file_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -247,6 +254,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.1+1"
|
||||
permission_handler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -282,6 +296,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
sembast:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sembast
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.2+2"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -323,7 +344,7 @@ packages:
|
||||
source: hosted
|
||||
version: "1.5.5"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: stack_trace
|
||||
url: "https://pub.dartlang.org"
|
||||
@@ -343,6 +364,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
synchronized:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: synchronized
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -413,6 +441,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.5.0"
|
||||
xxtea:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xxtea
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -13,12 +13,14 @@ dependencies:
|
||||
sdk: flutter
|
||||
flutter_advanced_networkimage: ^0.6.2
|
||||
flutter_sticky_header: ^0.4.0
|
||||
f_logs: ^1.2.2
|
||||
http: ^0.12.0+2
|
||||
intl: ^0.16.0
|
||||
package_info: ^0.4.0+13
|
||||
path_provider: ^1.5.1
|
||||
percent_indicator: ^2.1.1+1
|
||||
shared_preferences: ^0.5.6
|
||||
stack_trace: ^1.9.3
|
||||
url_launcher: ^5.4.1
|
||||
validators: ^2.0.0+1
|
||||
|
||||
|
||||
Reference in New Issue
Block a user