[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:
Jagandeep Brar
2020-01-06 17:30:17 -06:00
committed by GitHub
parent c45d1e45c5
commit 9dc1099742
48 changed files with 1569 additions and 212 deletions

View File

@@ -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

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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,

View File

@@ -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;
}
}

View File

@@ -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 {

View File

@@ -49,9 +49,11 @@ class _LidarrState extends State<StatefulWidget> {
];
void _navOnTap(int index) {
setState(() {
_currIndex = index;
});
if(mounted) {
setState(() {
_currIndex = index;
});
}
}
@override

View File

@@ -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];
});

View File

@@ -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;
});

View File

@@ -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;
}

View File

@@ -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];
});

View File

@@ -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,

View File

@@ -49,9 +49,11 @@ class _RadarrState extends State<StatefulWidget> {
];
void _navOnTap(int index) {
setState(() {
_currIndex = index;
});
if(mounted) {
setState(() {
_currIndex = index;
});
}
}
@override

View File

@@ -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];
});

View File

@@ -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;
});

View File

@@ -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];
});

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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,

View File

@@ -143,9 +143,11 @@ class _SABnzbdHistoryState extends State<StatefulWidget> with TickerProviderStat
)
),
onPressed: () async {
setState(() {
_hideCompleted = !_hideCompleted;
});
if(mounted) {
setState(() {
_hideCompleted = !_hideCompleted;
});
}
},
),
scale: _animationContoller,

View File

@@ -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];
});

View File

@@ -42,9 +42,11 @@ class _SettingsState extends State<StatefulWidget> {
];
void _navOnTap(int index) {
setState(() {
_currIndex = index;
});
if(mounted) {
setState(() {
_currIndex = index;
});
}
}
@override

View File

@@ -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];
});

View File

@@ -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];
});

View File

@@ -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];
});

View File

@@ -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];
});

View File

@@ -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(),
],
),

View 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(),
),
);
}
}

View 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,
);
}
}

View 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),
),
);
}
}

View 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),
),
);
}
}

View File

@@ -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() {

View File

@@ -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

View File

@@ -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';

View File

@@ -53,9 +53,11 @@ class _SonarrState extends State<StatefulWidget> {
];
void _navOnTap(int index) {
setState(() {
_currIndex = index;
});
if(mounted) {
setState(() {
_currIndex = index;
});
}
}
@override

View File

@@ -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];
});

View File

@@ -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;
});

View File

@@ -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];
});

View File

@@ -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,

View File

@@ -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);

View File

@@ -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
View 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();
}
}

View File

@@ -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];
}
}

View File

@@ -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:

View File

@@ -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