diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index d5157cd9..ad897fe6 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -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
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 153addc6..d4aefc7c 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -59,5 +59,12 @@
UIViewControllerBasedStatusBarAppearance
+
+ NSPhotoLibraryAddUsageDescription
+ LunaSea would like to save photos from the app to your gallery
+ NSPhotoLibraryUsageDescription
+ LunaSea would like to access your photo gallery for uploading images to the app
diff --git a/lib/logic/automation/lidarr/api.dart b/lib/logic/automation/lidarr/api.dart
index 1834a542..040f587b 100644
--- a/lib/logic/automation/lidarr/api.dart
+++ b/lib/logic/automation/lidarr/api.dart
@@ -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 testConnection(List 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' HTTP Status Code (${response.statusCode})', null);
}
+ } else {
+ logError('editArtist', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' HTTP Status Code (${response.statusCode})', null);
}
+ } else {
+ logError('toggleArtistMonitored', ' 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', ' HTTP Status Code (${response.statusCode})', null);
}
+ } else {
+ logError('downloadRelease', ' 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', ' 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', ' 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> getReleases(int albumId) async {
+ static Future> getReleases(int albumID) async {
List 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', ' 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', ' 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', ' 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;
}
}
diff --git a/lib/logic/automation/radarr/api.dart b/lib/logic/automation/radarr/api.dart
index 25a7c9e7..51a0d9b5 100644
--- a/lib/logic/automation/radarr/api.dart
+++ b/lib/logic/automation/radarr/api.dart
@@ -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 testConnection(List 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' HTTP Status Code (${response.statusCode})', null);
}
+ } else {
+ logError('editMovie', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' HTTP Status Code (${response.statusCode})', null);
}
+ } else {
+ logError('toggleMovieMonitored', ' 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', ' 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> getReleases(int movieId) async {
+ static Future> getReleases(int movieID) async {
List 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', ' 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', ' 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', ' 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> getRootFolders() async {
@@ -771,10 +866,14 @@ class RadarrAPI {
));
}
return _entries;
+ } else {
+ logError('getRootFolders', ' 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;
}
}
\ No newline at end of file
diff --git a/lib/logic/automation/sonarr/api.dart b/lib/logic/automation/sonarr/api.dart
index 19761478..4f058f69 100644
--- a/lib/logic/automation/sonarr/api.dart
+++ b/lib/logic/automation/sonarr/api.dart
@@ -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 testConnection(List 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', ' 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 refreshSeries(int id) async {
+ static Future refreshSeries(int seriesID) async {
List 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', ' 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 removeSeries(int id) async {
+ static Future removeSeries(int seriesID) async {
List 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', ' 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', ' 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', ' 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', ' HTTP Status Code (${response.statusCode})', null);
}
+ } else {
+ logError('editSeries', ' 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 getSeries(int id) async {
+ static Future getSeries(int seriesID) async {
List values = Values.sonarrValues;
if(values[0] == false) {
return null;
@@ -215,7 +252,7 @@ class SonarrAPI {
try {
Map _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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' HTTP Status Code (${response.statusCode})', null);
}
+ } else {
+ logError('toggleSeriesMonitored', ' 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', ' HTTP Status Code (${response.statusCode})', null);
}
+ } else {
+ logError('toggleSeasonMonitored', ' 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', ' 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', ' 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', ' 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', ' 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;
}
}
diff --git a/lib/logic/automation/sonarr/entry/episode.dart b/lib/logic/automation/sonarr/entry/episode.dart
index e36e65d1..344f442c 100644
--- a/lib/logic/automation/sonarr/entry/episode.dart
+++ b/lib/logic/automation/sonarr/entry/episode.dart
@@ -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,
diff --git a/lib/logic/clients/sabnzbd/api.dart b/lib/logic/clients/sabnzbd/api.dart
index 4c9dbc1b..c89d4a43 100644
--- a/lib/logic/clients/sabnzbd/api.dart
+++ b/lib/logic/clients/sabnzbd/api.dart
@@ -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 testConnection(List 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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', ' 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;
}
}
diff --git a/lib/main.dart b/lib/main.dart
index d8d700a0..1c6b891c 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -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>(() async {
+ runApp(_BIOS());
+ }, onError: (Object error, StackTrace stack) {
+ Logger.fatal(error, stack);
+ });
}
class _BIOS extends StatelessWidget {
diff --git a/lib/pages/lidarr/lidarr.dart b/lib/pages/lidarr/lidarr.dart
index 3fd935be..f25cb19e 100644
--- a/lib/pages/lidarr/lidarr.dart
+++ b/lib/pages/lidarr/lidarr.dart
@@ -49,9 +49,11 @@ class _LidarrState extends State {
];
void _navOnTap(int index) {
- setState(() {
- _currIndex = index;
- });
+ if(mounted) {
+ setState(() {
+ _currIndex = index;
+ });
+ }
}
@override
diff --git a/lib/pages/lidarr/subpages/catalogue/addartist/details.dart b/lib/pages/lidarr/subpages/catalogue/addartist/details.dart
index 28b85704..234303ae 100644
--- a/lib/pages/lidarr/subpages/catalogue/addartist/details.dart
+++ b/lib/pages/lidarr/subpages/catalogue/addartist/details.dart
@@ -169,9 +169,11 @@ class _LidarrArtistSearchDetailsState extends State {
trailing: Switch(
value: _monitored,
onChanged: (value) {
- setState(() {
- _monitored = value;
- });
+ if(mounted) {
+ setState(() {
+ _monitored = value;
+ });
+ }
},
),
),
@@ -186,9 +188,11 @@ class _LidarrArtistSearchDetailsState extends State {
trailing: Switch(
value: _albumFolders,
onChanged: (value) {
- setState(() {
- _albumFolders = value;
- });
+ if(mounted) {
+ setState(() {
+ _albumFolders = value;
+ });
+ }
},
),
),
@@ -205,7 +209,7 @@ class _LidarrArtistSearchDetailsState extends State {
),
onTap: () async {
List _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 {
),
onTap: () async {
List _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 {
),
onTap: () async {
List _values = await LidarrDialogs.showEditMetadataProfilePrompt(context, _metadataProfiles);
- if(_values[0]) {
+ if(_values[0] && mounted) {
setState(() {
_metadataProfile = _values[1];
});
diff --git a/lib/pages/lidarr/subpages/catalogue/catalogue.dart b/lib/pages/lidarr/subpages/catalogue/catalogue.dart
index a6d83b22..ead5bf26 100644
--- a/lib/pages/lidarr/subpages/catalogue/catalogue.dart
+++ b/lib/pages/lidarr/subpages/catalogue/catalogue.dart
@@ -60,12 +60,14 @@ class _CatalogueState extends State 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 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 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 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 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;
});
diff --git a/lib/pages/lidarr/subpages/details/artist.dart b/lib/pages/lidarr/subpages/details/artist.dart
index 16632201..cf1bdd40 100644
--- a/lib/pages/lidarr/subpages/details/artist.dart
+++ b/lib/pages/lidarr/subpages/details/artist.dart
@@ -129,9 +129,11 @@ class _LidarrArtistDetailsState extends State {
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 {
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 {
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;
}
diff --git a/lib/pages/lidarr/subpages/details/edit.dart b/lib/pages/lidarr/subpages/details/edit.dart
index addd1561..56e9b406 100644
--- a/lib/pages/lidarr/subpages/details/edit.dart
+++ b/lib/pages/lidarr/subpages/details/edit.dart
@@ -153,9 +153,11 @@ class _LidarrEditArtistState extends State {
trailing: Switch(
value: _monitored,
onChanged: (value) {
- setState(() {
- _monitored = value;
- });
+ if(mounted) {
+ setState(() {
+ _monitored = value;
+ });
+ }
},
),
),
@@ -169,9 +171,11 @@ class _LidarrEditArtistState extends State {
trailing: Switch(
value: _albumFolders,
onChanged: (value) {
- setState(() {
- _albumFolders = value;
- });
+ if(mounted) {
+ setState(() {
+ _albumFolders = value;
+ });
+ }
},
),
),
@@ -188,7 +192,7 @@ class _LidarrEditArtistState extends State {
),
onTap: () async {
List _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 {
),
onTap: () async {
List _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 {
),
onTap: () async {
List _values = await LidarrDialogs.showEditMetadataProfilePrompt(context, _metadataProfiles);
- if(_values[0]) {
+ if(_values[0] && mounted) {
setState(() {
_metadataProfile = _values[1];
});
diff --git a/lib/pages/lidarr/subpages/details/release.dart b/lib/pages/lidarr/subpages/details/release.dart
index 9afee074..c9b74b78 100644
--- a/lib/pages/lidarr/subpages/details/release.dart
+++ b/lib/pages/lidarr/subpages/details/release.dart
@@ -125,6 +125,10 @@ class _LidarrReleaseInfoState extends State {
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,
diff --git a/lib/pages/radarr/radarr.dart b/lib/pages/radarr/radarr.dart
index 6974fb13..0eb8e570 100644
--- a/lib/pages/radarr/radarr.dart
+++ b/lib/pages/radarr/radarr.dart
@@ -49,9 +49,11 @@ class _RadarrState extends State {
];
void _navOnTap(int index) {
- setState(() {
- _currIndex = index;
- });
+ if(mounted) {
+ setState(() {
+ _currIndex = index;
+ });
+ }
}
@override
diff --git a/lib/pages/radarr/subpages/catalogue/addmovie/details.dart b/lib/pages/radarr/subpages/catalogue/addmovie/details.dart
index 6ab8ce93..e6b80381 100644
--- a/lib/pages/radarr/subpages/catalogue/addmovie/details.dart
+++ b/lib/pages/radarr/subpages/catalogue/addmovie/details.dart
@@ -163,9 +163,11 @@ class _RadarrMovieSearchDetailsState extends State {
trailing: Switch(
value: monitored,
onChanged: (value) {
- setState(() {
- monitored = value;
- });
+ if(mounted) {
+ setState(() {
+ monitored = value;
+ });
+ }
},
),
),
@@ -183,9 +185,11 @@ class _RadarrMovieSearchDetailsState extends State {
onTap: () async {
List _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 {
),
onTap: () async {
List _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 {
),
onTap: () async {
List _values = await RadarrDialogs.showMinimumAvailabilityPrompt(context, Constants.radarrMinAvailability);
- if(_values[0]) {
+ if(_values[0] && mounted) {
setState(() {
_minimumAvailability = _values[1];
});
diff --git a/lib/pages/radarr/subpages/catalogue/catalogue.dart b/lib/pages/radarr/subpages/catalogue/catalogue.dart
index 0fbc2cc8..47d7a82f 100644
--- a/lib/pages/radarr/subpages/catalogue/catalogue.dart
+++ b/lib/pages/radarr/subpages/catalogue/catalogue.dart
@@ -61,12 +61,14 @@ class _CatalogueState extends State 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 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 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 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 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;
});
diff --git a/lib/pages/radarr/subpages/details/edit.dart b/lib/pages/radarr/subpages/details/edit.dart
index 1de409d8..64d8c6b3 100644
--- a/lib/pages/radarr/subpages/details/edit.dart
+++ b/lib/pages/radarr/subpages/details/edit.dart
@@ -136,9 +136,11 @@ class _RadarrEditMovieState extends State {
trailing: Switch(
value: _monitored,
onChanged: (value) {
- setState(() {
- _monitored = value;
- });
+ if(mounted) {
+ setState(() {
+ _monitored = value;
+ });
+ }
},
),
),
@@ -152,9 +154,11 @@ class _RadarrEditMovieState extends State {
trailing: Switch(
value: _static,
onChanged: (value) {
- setState(() {
- _static = value;
- });
+ if(mounted) {
+ setState(() {
+ _static = value;
+ });
+ }
},
),
),
@@ -171,7 +175,7 @@ class _RadarrEditMovieState extends State {
),
onTap: () async {
List _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 {
),
onTap: () async {
List _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 {
),
onTap: () async {
List _values = await RadarrDialogs.showMinimumAvailabilityPrompt(context, Constants.radarrMinAvailability);
- if(_values[0]) {
+ if(_values[0] && mounted) {
setState(() {
_minimumAvailability = _values[1];
});
diff --git a/lib/pages/radarr/subpages/details/movie.dart b/lib/pages/radarr/subpages/details/movie.dart
index 48aa2bdf..56a8e5ca 100644
--- a/lib/pages/radarr/subpages/details/movie.dart
+++ b/lib/pages/radarr/subpages/details/movie.dart
@@ -88,9 +88,11 @@ class _RadarrMovieDetailsState extends State {
}
void _needFetch() {
- setState(() {
- _loading = true;
- });
+ if(mounted) {
+ setState(() {
+ _loading = true;
+ });
+ }
}
Future _refreshData() async {
@@ -219,9 +221,11 @@ class _RadarrMovieDetailsState extends State {
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;
}
diff --git a/lib/pages/radarr/subpages/details/release.dart b/lib/pages/radarr/subpages/details/release.dart
index bf7ee377..ccc0232e 100644
--- a/lib/pages/radarr/subpages/details/release.dart
+++ b/lib/pages/radarr/subpages/details/release.dart
@@ -125,6 +125,10 @@ class _RadarrReleaseInfoState extends State {
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,
diff --git a/lib/pages/sabnzbd/sabnzbd.dart b/lib/pages/sabnzbd/sabnzbd.dart
index b82ada5e..6bf56d8c 100644
--- a/lib/pages/sabnzbd/sabnzbd.dart
+++ b/lib/pages/sabnzbd/sabnzbd.dart
@@ -52,9 +52,11 @@ class _SABnzbdState extends State {
];
void _navOnTap(int index) {
- setState(() {
- _currIndex = index;
- });
+ if(mounted) {
+ setState(() {
+ _currIndex = index;
+ });
+ }
}
void _refreshStatus(SABnzbdStatusEntry entry) {
diff --git a/lib/pages/sabnzbd/subpages/history/details/details.dart b/lib/pages/sabnzbd/subpages/history/details/details.dart
index 71eb4f9e..b0d446c6 100644
--- a/lib/pages/sabnzbd/subpages/history/details/details.dart
+++ b/lib/pages/sabnzbd/subpages/history/details/details.dart
@@ -179,6 +179,10 @@ class _SABnzbdHistoryDetailsState extends State {
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 {
String data = stage['actions'].join(',\n').replaceAll('
', '.\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,
diff --git a/lib/pages/sabnzbd/subpages/history/history.dart b/lib/pages/sabnzbd/subpages/history/history.dart
index 1affa62a..e0c25a6d 100644
--- a/lib/pages/sabnzbd/subpages/history/history.dart
+++ b/lib/pages/sabnzbd/subpages/history/history.dart
@@ -143,9 +143,11 @@ class _SABnzbdHistoryState extends State with TickerProviderStat
)
),
onPressed: () async {
- setState(() {
- _hideCompleted = !_hideCompleted;
- });
+ if(mounted) {
+ setState(() {
+ _hideCompleted = !_hideCompleted;
+ });
+ }
},
),
scale: _animationContoller,
diff --git a/lib/pages/sabnzbd/subpages/queue/queue.dart b/lib/pages/sabnzbd/subpages/queue/queue.dart
index 9892a604..b35382f2 100644
--- a/lib/pages/sabnzbd/subpages/queue/queue.dart
+++ b/lib/pages/sabnzbd/subpages/queue/queue.dart
@@ -183,13 +183,13 @@ class _SABnzbdQueueState extends State {
),
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 {
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 {
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 {
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];
});
diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart
index 6cb3c9aa..e882f5d2 100644
--- a/lib/pages/settings/settings.dart
+++ b/lib/pages/settings/settings.dart
@@ -42,9 +42,11 @@ class _SettingsState extends State {
];
void _navOnTap(int index) {
- setState(() {
- _currIndex = index;
- });
+ if(mounted) {
+ setState(() {
+ _currIndex = index;
+ });
+ }
}
@override
diff --git a/lib/pages/settings/subpages/automation/tabs/lidarr.dart b/lib/pages/settings/subpages/automation/tabs/lidarr.dart
index 52e6730b..01c47bae 100644
--- a/lib/pages/settings/subpages/automation/tabs/lidarr.dart
+++ b/lib/pages/settings/subpages/automation/tabs/lidarr.dart
@@ -37,9 +37,11 @@ class _LidarrState extends State {
}
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 {
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 {
),
onTap: () async {
List _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 {
),
onTap: () async {
List _values = await SystemDialogs.showEditTextPrompt(context, 'Lidarr API Key', prefill: _lidarrValues[2]);
- if(_values[0]) {
+ if(_values[0] && mounted) {
setState(() {
_lidarrValues[2] = _values[1];
});
diff --git a/lib/pages/settings/subpages/automation/tabs/radarr.dart b/lib/pages/settings/subpages/automation/tabs/radarr.dart
index 338cf63e..3eca3897 100644
--- a/lib/pages/settings/subpages/automation/tabs/radarr.dart
+++ b/lib/pages/settings/subpages/automation/tabs/radarr.dart
@@ -37,9 +37,11 @@ class _RadarrState extends State {
}
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 {
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 {
),
onTap: () async {
List _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 {
),
onTap: () async {
List _values = await SystemDialogs.showEditTextPrompt(context, 'Radarr API Key', prefill: _radarrValues[2]);
- if(_values[0]) {
+ if(_values[0] && mounted) {
setState(() {
_radarrValues[2] = _values[1];
});
diff --git a/lib/pages/settings/subpages/automation/tabs/sonarr.dart b/lib/pages/settings/subpages/automation/tabs/sonarr.dart
index 730444b9..3b401e69 100644
--- a/lib/pages/settings/subpages/automation/tabs/sonarr.dart
+++ b/lib/pages/settings/subpages/automation/tabs/sonarr.dart
@@ -37,9 +37,11 @@ class _SonarrState extends State {
}
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 {
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 {
),
onTap: () async {
List _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 {
),
onTap: () async {
List _values = await SystemDialogs.showEditTextPrompt(context, 'Sonarr API Key', prefill: _sonarrValues[2]);
- if(_values[0]) {
+ if(_values[0] && mounted) {
setState(() {
_sonarrValues[2] = _values[1];
});
diff --git a/lib/pages/settings/subpages/clients/tabs/sabnzbd.dart b/lib/pages/settings/subpages/clients/tabs/sabnzbd.dart
index e6060744..8a19c103 100644
--- a/lib/pages/settings/subpages/clients/tabs/sabnzbd.dart
+++ b/lib/pages/settings/subpages/clients/tabs/sabnzbd.dart
@@ -37,9 +37,11 @@ class _SABnzbdState extends State {
}
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 {
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 {
),
onTap: () async {
List _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 {
),
onTap: () async {
List _values = await SystemDialogs.showEditTextPrompt(context, 'SABnzbd API Key', prefill: _sabnzbdValues[2]);
- if(_values[0]) {
+ if(_values[0] && mounted) {
setState(() {
_sabnzbdValues[2] = _values[1];
});
diff --git a/lib/pages/settings/subpages/general/general.dart b/lib/pages/settings/subpages/general/general.dart
index 3c057a63..b8d136fc 100644
--- a/lib/pages/settings/subpages/general/general.dart
+++ b/lib/pages/settings/subpages/general/general.dart
@@ -20,6 +20,7 @@ class _GeneralState extends State {
final List _titles = [
'Profile',
'Configuration',
+ 'Logs',
'LunaSea',
];
@@ -33,6 +34,7 @@ class _GeneralState extends State {
children: [
Profile(),
BackupRestore(),
+ Logs(),
LunaSea(),
],
),
diff --git a/lib/pages/settings/subpages/general/tabs/logs.dart b/lib/pages/settings/subpages/general/tabs/logs.dart
new file mode 100644
index 00000000..aaef52e8
--- /dev/null
+++ b/lib/pages/settings/subpages/general/tabs/logs.dart
@@ -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 createState() {
+ return _LogsState();
+ }
+}
+
+class _LogsState extends State {
+ final _scaffoldKey = GlobalKey();
+
+ @override
+ void initState() {
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ key: _scaffoldKey,
+ body: _logSettings(),
+ );
+ }
+
+ Widget _logSettings() {
+ return Scrollbar(
+ child: ListView(
+ children: [
+ 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 _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 _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 _viewLogs() async {
+ await Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (context) => TypeLogs(),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/settings/subpages/general/tabs/logs/details.dart b/lib/pages/settings/subpages/general/tabs/logs/details.dart
new file mode 100644
index 00000000..b4aa6a8d
--- /dev/null
+++ b/lib/pages/settings/subpages/general/tabs/logs/details.dart
@@ -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 createState() {
+ return _LogDetailsState(log: log);
+ }
+}
+
+class _LogDetailsState extends State {
+ final FLog.Log log;
+ final _scaffoldKey = GlobalKey();
+
+ _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: [
+ _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,
+ );
+ }
+}
diff --git a/lib/pages/settings/subpages/general/tabs/logs/type.dart b/lib/pages/settings/subpages/general/tabs/logs/type.dart
new file mode 100644
index 00000000..3e6c2b91
--- /dev/null
+++ b/lib/pages/settings/subpages/general/tabs/logs/type.dart
@@ -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 createState() {
+ return _TypeLogsState();
+ }
+}
+
+class _TypeLogsState extends State {
+ final _scaffoldKey = GlobalKey();
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ key: _scaffoldKey,
+ appBar: Navigation.getAppBar('Log Types', context),
+ body: _buildList(),
+ );
+ }
+
+ Widget _buildList() {
+ return Scrollbar(
+ child: ListView(
+ children: [
+ 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 _viewLogs(String type) async {
+ await Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (context) => ViewLogs(type: type),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/settings/subpages/general/tabs/logs/view.dart b/lib/pages/settings/subpages/general/tabs/logs/view.dart
new file mode 100644
index 00000000..6c4736e6
--- /dev/null
+++ b/lib/pages/settings/subpages/general/tabs/logs/view.dart
@@ -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 createState() {
+ return _ViewLogsState(type: type);
+ }
+}
+
+class _ViewLogsState extends State {
+ final String type;
+ final _scaffoldKey = GlobalKey();
+ bool _loading = true;
+ List _logs;
+ List 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 _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 _logDetails(FLog.Log log) async {
+ await Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (context) => LogDetails(log: log),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/settings/subpages/general/tabs/lunasea.dart b/lib/pages/settings/subpages/general/tabs/lunasea.dart
index 1f30c62a..e8370ff0 100644
--- a/lib/pages/settings/subpages/general/tabs/lunasea.dart
+++ b/lib/pages/settings/subpages/general/tabs/lunasea.dart
@@ -39,10 +39,12 @@ class _LunaSeaState extends State {
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() {
diff --git a/lib/pages/settings/subpages/general/tabs/profile.dart b/lib/pages/settings/subpages/general/tabs/profile.dart
index eebeb1ed..fccb76db 100644
--- a/lib/pages/settings/subpages/general/tabs/profile.dart
+++ b/lib/pages/settings/subpages/general/tabs/profile.dart
@@ -29,10 +29,12 @@ class _ProfileState extends State {
}
void _refreshData() {
- setState(() {
- _enabled = Profiles.enabledProfile;
- _profiles = Profiles.profileList;
- });
+ if(mounted) {
+ setState(() {
+ _enabled = Profiles.enabledProfile;
+ _profiles = Profiles.profileList;
+ });
+ }
}
@override
diff --git a/lib/pages/settings/subpages/general/tabs/tabs.dart b/lib/pages/settings/subpages/general/tabs/tabs.dart
index bee8eedd..47a30255 100644
--- a/lib/pages/settings/subpages/general/tabs/tabs.dart
+++ b/lib/pages/settings/subpages/general/tabs/tabs.dart
@@ -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';
diff --git a/lib/pages/sonarr/sonarr.dart b/lib/pages/sonarr/sonarr.dart
index 9690d3ba..8ab4f4b3 100644
--- a/lib/pages/sonarr/sonarr.dart
+++ b/lib/pages/sonarr/sonarr.dart
@@ -53,9 +53,11 @@ class _SonarrState extends State {
];
void _navOnTap(int index) {
- setState(() {
- _currIndex = index;
- });
+ if(mounted) {
+ setState(() {
+ _currIndex = index;
+ });
+ }
}
@override
diff --git a/lib/pages/sonarr/subpages/catalogue/addseries/details.dart b/lib/pages/sonarr/subpages/catalogue/addseries/details.dart
index 7bff7e77..6be98a3f 100644
--- a/lib/pages/sonarr/subpages/catalogue/addseries/details.dart
+++ b/lib/pages/sonarr/subpages/catalogue/addseries/details.dart
@@ -165,9 +165,11 @@ class _SonarrSeriesSearchDetailsState extends State {
trailing: Switch(
value: monitored,
onChanged: (value) {
- setState(() {
- monitored = value;
- });
+ if(mounted) {
+ setState(() {
+ monitored = value;
+ });
+ }
},
),
),
@@ -181,9 +183,11 @@ class _SonarrSeriesSearchDetailsState extends State {
trailing: Switch(
value: seasonFolders,
onChanged: (value) {
- setState(() {
- seasonFolders = value;
- });
+ if(mounted) {
+ setState(() {
+ seasonFolders = value;
+ });
+ }
},
),
),
@@ -200,7 +204,7 @@ class _SonarrSeriesSearchDetailsState extends State {
),
onTap: () async {
List _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 {
),
onTap: () async {
List _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 {
),
onTap: () async {
List _values = await SonarrDialogs.showEditSeriesTypePrompt(context);
- if(_values[0]) {
+ if(_values[0] && mounted) {
setState(() {
_seriesType = _values[1];
});
diff --git a/lib/pages/sonarr/subpages/catalogue/catalogue.dart b/lib/pages/sonarr/subpages/catalogue/catalogue.dart
index b9e9b90e..c0666958 100644
--- a/lib/pages/sonarr/subpages/catalogue/catalogue.dart
+++ b/lib/pages/sonarr/subpages/catalogue/catalogue.dart
@@ -60,12 +60,14 @@ class _CatalogueState extends State 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 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 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 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