[Release/TestFlight] v4.1.0+40100001 (#266)

NEW
- [Networking] Strict TLS/SSL validation is now disabled globally
- [Routing] Hold the AppBar back button to pop back to the home page of the module
- [Settings/Sonarr] Toggle to enable Sonarr v3 features
- [Sonarr] Complete rewrite of Sonarr
- [Sonarr] State is now held across module switches
- [Sonarr/Overview] View tags applied to series
- [Sonarr/Catalogue] (v3 only) Ability to set and update language profile
- [Sonarr/Catalogue] Ability to view all, only monitored, or only unmonitored series
- [Sonarr/Releases] (v3 only) Ability to interactively search for season packs
- [Sonarr/Releases] Ability to view all, only approved, or only rejected releases

TWEAKS
- [Settings] Removed all toggles for strict TLS
- [Sonarr] Many tweaks to Sonarr's design
- [Sonarr/Add] (v3 only) Tapping an already-added series will take you to the series page
- [Sonarr/Add] Automatically navigate to newly added series
- [Sonarr/Series] Toggling monitored state of series has now been moved to the edit prompt

FIXES
- [Images] Images will now load for invalid/self-signed certificates
- [Tautulli/Activity] Fix grey screen for music activity
- [TextField] Fix TextField actions (cut, copy, paste, etc.) not showing
- [Timestamps] Fix 12:xx AM being shown as 00:xx AM
- Additional small bug fixes
This commit is contained in:
Jagandeep Brar
2020-10-13 09:43:28 -05:00
committed by GitHub
parent 0530fa3dee
commit 5cf33b3abd
597 changed files with 10219 additions and 9385 deletions

View File

@@ -3,4 +3,4 @@ export 'core/constants.dart';
export 'core/database.dart';
export 'core/dialogs.dart';
export 'core/sorting.dart';
export 'core/state_global.dart';
export 'core/state.dart';

View File

@@ -35,7 +35,7 @@ class NewznabAPI extends API {
String get host => _values['host'];
String get key => _values['key'];
void logWarning(String methodName, String text) => Logger.warning(
void logWarning(String methodName, String text) => LunaLogger.warning(
'package:lunasea/core/api/newznab/api.dart',
methodName,
'Newznab: $text',
@@ -43,7 +43,7 @@ class NewznabAPI extends API {
void logError(String methodName, String text, Object error, StackTrace trace, {
bool uploadToSentry = true,
}) => Logger.error(
}) => LunaLogger.error(
'package:lunasea/core/api/newznab/api.dart',
methodName,
'Newznab: $text',

View File

@@ -6,13 +6,13 @@ class SearchConstants {
static const String MODULE_KEY = 'search';
static const ModuleMap MODULE_MAP = ModuleMap(
static const LunaModuleMap MODULE_MAP = LunaModuleMap(
name: 'Search',
description: 'Search Newznab Indexers',
settingsDescription: 'Configure Search',
icon: Icons.search,
route: '/search',
color: Color(Constants.ACCENT_COLOR),
color: Color(LunaColours.ACCENT_COLOR),
);
//ignore: non_constant_identifier_names

View File

@@ -19,7 +19,7 @@ class SearchDialogs {
builder: (BuildContext context) => AlertDialog(
title: LSDialog.title(text: 'Download'),
actions: <Widget>[
LSDialog.cancel(context, textColor: LSColors.accent),
LSDialog.cancel(context, textColor: LunaColours.accent),
],
content: ValueListenableBuilder(
valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]),
@@ -46,7 +46,7 @@ class SearchDialogs {
),
LSIcon(
icon: Icons.arrow_drop_down,
color: LSColors.accent,
color: LunaColours.accent,
),
],
),
@@ -54,16 +54,13 @@ class SearchDialogs {
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: LSColors.accent,
color: LunaColours.accent,
width: 2.0,
),
),
),
),
onSelected: (result) {
LunaSeaDatabaseValue.ENABLED_PROFILE.put(result);
Providers.reset(context);
},
onSelected: (result) => LunaProfile.changeProfile(context, result),
itemBuilder: (context) {
return <PopupMenuEntry<String>>[for(String profile in (profilesBox as Box).keys) PopupMenuItem<String>(
value: profile,
@@ -80,19 +77,19 @@ class SearchDialogs {
),
if(Database.currentProfileObject.sabnzbdEnabled) LSDialog.tile(
icon: CustomIcons.sabnzbd,
iconColor: LSColors.list(0),
iconColor: LunaColours.list(0),
text: 'SABnzbd',
onTap: () => _setValues(true, 'sabnzbd'),
),
if(Database.currentProfileObject.nzbgetEnabled) LSDialog.tile(
icon: CustomIcons.nzbget,
iconColor: LSColors.list(1),
iconColor: LunaColours.list(1),
text: 'NZBGet',
onTap: () => _setValues(true, 'nzbget'),
),
LSDialog.tile(
icon: Icons.file_download,
iconColor: LSColors.list(2),
iconColor: LunaColours.list(2),
text: 'Download to Device',
onTap: () => _setValues(true, 'filesystem'),
),

View File

@@ -34,7 +34,7 @@ extension SearchResultsSortingExtension on SearchResultsSorting {
) => _sorter.byType(data, this, ascending);
}
class _Sorter extends Sorter<SearchResultsSorting> {
class _Sorter extends LunaSorter<SearchResultsSorting> {
@override
List byType(
List data,

View File

@@ -0,0 +1,2 @@
export 'state/global.dart';
export 'state/local.dart';

View File

@@ -1,8 +1,14 @@
import 'package:flutter/foundation.dart';
import 'package:lunasea/core.dart';
import 'package:lunasea/modules/search.dart';
class SearchModel extends ChangeNotifier {
class SearchState extends LunaGlobalState {
SearchState() {
reset();
}
@override
void reset() {}
IndexerHiveObject _indexer;
IndexerHiveObject get indexer => _indexer;
set indexer(IndexerHiveObject indexer) {

View File

View File

@@ -28,14 +28,16 @@ class _State extends State<SearchCategories> {
);
Future<void> _refresh() async {
final model = Provider.of<SearchModel>(context, listen: false);
final model = Provider.of<SearchState>(context, listen: false);
if(mounted) setState(() {
_future = NewznabAPI.from(model?.indexer)?.getCategories();
});
}
Widget get _appBar => LSAppBar(
title: Provider.of<SearchModel>(context, listen: false)?.indexer?.displayName ?? 'Categories',
Widget get _appBar => LunaAppBar(
context: context,
popUntil: '/search',
title: Provider.of<SearchState>(context, listen: false)?.indexer?.displayName ?? 'Categories',
actions: <Widget>[
LSIconButton(
icon: Icons.search,
@@ -79,7 +81,7 @@ class _State extends State<SearchCategories> {
);
Future<void> _enterSearch() async {
final model = Provider.of<SearchModel>(context, listen: false);
final model = Provider.of<SearchState>(context, listen: false);
model.searchTitle = '${model?.indexer?.displayName}';
model.searchCategoryID = -1;
model.searchQuery = '';

View File

@@ -37,7 +37,12 @@ class _State extends State<Search> {
),
);
Widget get _appBar => LSAppBar(title: 'Search');
Widget get _appBar => LunaAppBar(
context: context,
popUntil: null,
hideLeading: true,
title: 'Search',
);
Widget get _drawer => LSDrawer(page: 'search');

View File

@@ -31,16 +31,18 @@ class _State extends State<SearchResults> {
);
Future<void> _refresh() async {
final model = Provider.of<SearchModel>(context, listen: false);
final model = Provider.of<SearchState>(context, listen: false);
if(mounted) setState(() => { _future = NewznabAPI.from(model?.indexer).getResults(
categoryId: model?.searchCategoryID,
query: '',
)});
Future.microtask(() => Provider.of<SearchModel>(context, listen: false)?.searchResultsFilter = '');
Future.microtask(() => Provider.of<SearchState>(context, listen: false)?.searchResultsFilter = '');
}
Widget get _appBar => LSAppBar(
title: Provider.of<SearchModel>(context, listen: false)?.searchTitle ?? 'Results',
Widget get _appBar => LunaAppBar(
context: context,
popUntil: '/search',
title: Provider.of<SearchState>(context, listen: false)?.searchTitle ?? 'Results',
actions: <Widget>[
LSIconButton(
icon: Icons.search,
@@ -77,7 +79,7 @@ class _State extends State<SearchResults> {
buttonText: 'Refresh',
onTapHandler: () => _refresh(),
)
: Consumer<SearchModel>(
: Consumer<SearchState>(
builder: (context, model, widget) {
List<NewznabResultData> _filtered = _sort(model, _filter(model.searchResultsFilter));
return _listBody(_filtered);
@@ -112,7 +114,7 @@ class _State extends State<SearchResults> {
);
Future<void> _enterSearch() async {
final model = Provider.of<SearchModel>(context, listen: false);
final model = Provider.of<SearchState>(context, listen: false);
model.searchQuery = '';
await Navigator.of(context).pushNamed(SearchSearch.ROUTE_NAME);
}
@@ -123,8 +125,8 @@ class _State extends State<SearchResults> {
: entry.title.toLowerCase().contains(filter.toLowerCase())
).toList();
List<NewznabResultData> _sort(SearchModel model, List<NewznabResultData> data) {
if(data != null && data.length != 0) return model.sortResultsSorting.sort(data, model.sortResultsAscending);
List<NewznabResultData> _sort(SearchState state, List<NewznabResultData> data) {
if(data != null && data.length != 0) return state.sortResultsSorting.sort(data, state.sortResultsAscending);
return data;
}
}

View File

@@ -26,7 +26,7 @@ class _State extends State<SearchSearch> {
}
Future<void> _refresh() async {
final model = Provider.of<SearchModel>(context, listen: false);
final model = Provider.of<SearchState>(context, listen: false);
if(mounted) setState(() {
_future = NewznabAPI.from(model?.indexer).getResults(
categoryId: model?.searchCategoryID,
@@ -35,12 +35,16 @@ class _State extends State<SearchSearch> {
});
}
Widget get _appBar => LSAppBar(title: 'Search: ${Provider.of<SearchModel>(context, listen: false)?.searchTitle ?? 'Unknown'}');
Widget get _appBar => LunaAppBar(
context: context,
popUntil: '/search',
title: 'Search: ${Provider.of<SearchState>(context, listen: false)?.searchTitle ?? 'Unknown'}',
);
Widget get _body => LSRefreshIndicator(
refreshKey: _refreshKey,
onRefresh: _refresh,
child: Consumer<SearchModel>(
child: Consumer<SearchState>(
builder: (context, model, widget) => FutureBuilder(
future: _future,
builder: (context, snapshot) {
@@ -90,11 +94,11 @@ class _State extends State<SearchSearch> {
List<Widget> get _error => [LSErrorMessage(onTapHandler: () => _refresh(), hideButton: true)];
List<Widget> _assembleResults(SearchModel model) {
List<Widget> _assembleResults(SearchState state) {
if(_results.length <= 0) {
return [LSGenericMessage(text: 'No Results Found')];
}
List<NewznabResultData> _sorted = _sort(model, _results);
List<NewznabResultData> _sorted = _sort(state, _results);
return List.generate(
_sorted.length,
(index) => SearchResultTile(
@@ -103,8 +107,8 @@ class _State extends State<SearchSearch> {
);
}
List<NewznabResultData> _sort(SearchModel model, List<NewznabResultData> data) {
if(data != null && data.length != 0) return model.sortResultsSorting.sort(data, model.sortResultsAscending);
List<NewznabResultData> _sort(SearchState state, List<NewznabResultData> data) {
if(data != null && data.length != 0) return state.sortResultsSorting.sort(data, state.sortResultsAscending);
return data;
}
}

View File

@@ -19,8 +19,10 @@ class _State extends State<SearchSubcategories> {
body: _body,
);
Widget get _appBar => LSAppBar(
title: Provider.of<SearchModel>(context, listen: false)?.category?.name ?? 'Subcategories',
Widget get _appBar => LunaAppBar(
context: context,
popUntil: '/search',
title: Provider.of<SearchState>(context, listen: false)?.category?.name ?? 'Subcategories',
actions: <Widget>[
LSIconButton(
icon: Icons.search,
@@ -29,7 +31,7 @@ class _State extends State<SearchSubcategories> {
],
);
Widget get _body => Consumer<SearchModel>(
Widget get _body => Consumer<SearchState>(
builder: (context, _state, child) => LSListViewBuilder(
itemCount: (_state?.category?.subcategories?.length ?? 0)+1,
itemBuilder: (context, index) => SearchSubcategoryTile(
@@ -41,7 +43,7 @@ class _State extends State<SearchSubcategories> {
);
Future<void> _enterSearch() async {
final model = Provider.of<SearchModel>(context, listen: false);
final model = Provider.of<SearchState>(context, listen: false);
model.searchTitle = '${model?.category?.name ?? ''}';
model.searchCategoryID = model?.category?.id ?? 0;
model.searchQuery = '';

View File

@@ -15,13 +15,13 @@ class SearchCategoryTile extends StatelessWidget {
Widget build(BuildContext context) => LSCardTile(
title: LSTitle(text: category.name),
subtitle: LSSubtitle(text: category.subcategories.length == 0 ? 'No Subcategories Available': category.subcategoriesList),
leading: LSIconButton(icon: category.icon, color: LSColors.list(index)),
leading: LSIconButton(icon: category.icon, color: LunaColours.list(index)),
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
onTap: () async => _enterSubcategories(context),
);
Future<void> _enterSubcategories(BuildContext context) async {
final model = Provider.of<SearchModel>(context, listen: false);
final model = Provider.of<SearchState>(context, listen: false);
model?.category = category;
model?.searchCategoryID = category.id;
await Navigator.of(context).pushNamed(SearchSubcategories.ROUTE_NAME);

View File

@@ -33,7 +33,7 @@ class SearchDetailsDownloadButton extends StatelessWidget {
case 'sabnzbd': _sendToSABnzbd(context, data); break;
case 'nzbget': _sendToNZBGet(context, data); break;
case 'filesystem': _downloadToFilesystem(context); break;
default: Logger.warning('SearchDetailsDownloadButton', '_sendToClient', 'Unknown case: ${_values[1]}'); break;
default: LunaLogger.warning('SearchDetailsDownloadButton', '_sendToClient', 'Unknown case: ${_values[1]}'); break;
}
}
}
@@ -90,13 +90,13 @@ class SearchDetailsDownloadButton extends StatelessWidget {
),
).get(data.linkDownload);
if(response.statusCode == 200) {
await Filesystem.exportDownloadToFilesystem('${data.title}.nzb', response.data);
await LunaFileSystem.exportDownloadToFilesystem('${data.title}.nzb', response.data);
LSSnackBar(context: context, title: 'Downloaded NZB', message: 'Downloaded NZB to your device', type: SNACKBAR_TYPE.success);
} else {
throw Error();
}
} catch (error) {
Logger.error('SearchDetailsDownloadButton', '_downloadToFilesystem', 'Error downloading NZB', error, StackTrace.current);
LunaLogger.error('SearchDetailsDownloadButton', '_downloadToFilesystem', 'Error downloading NZB', error, StackTrace.current);
LSSnackBar(context: context, title: 'Failed to Download NZB', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure);
}
}

View File

@@ -16,12 +16,12 @@ class SearchIndexerTile extends StatelessWidget {
title: LSTitle(text: indexer.displayName),
subtitle: LSSubtitle(text: indexer.host),
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
leading: LSIconButton(icon: Icons.rss_feed, color: LSColors.list(index)),
leading: LSIconButton(icon: Icons.rss_feed, color: LunaColours.list(index)),
onTap: () async => _enterIndexer(context),
);
Future<void> _enterIndexer(BuildContext context) async {
Provider.of<SearchModel>(context, listen: false)?.indexer = indexer;
Provider.of<SearchState>(context, listen: false)?.indexer = indexer;
await Navigator.of(context).pushNamed(SearchCategories.ROUTE_NAME);
}
}

View File

@@ -24,18 +24,18 @@ class _State extends State<SearchResultsSearchBar> {
@override
Widget build(BuildContext context) => Expanded(
child: Consumer<SearchModel>(
builder: (context, model, widget) => LSTextInputBar(
child: Consumer<SearchState>(
builder: (context, state, widget) => LSTextInputBar(
controller: _textController,
labelText: 'Search Results...',
onChanged: (text, update) => _onChanged(model, text, update),
onChanged: (text, update) => _onChanged(state, text, update),
margin: EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 12.0),
),
),
);
void _onChanged(SearchModel model, String text, bool update) {
model.searchResultsFilter = text;
void _onChanged(SearchState state, String text, bool update) {
state.searchResultsFilter = text;
if(update) _textController.text = '';
}
}

View File

@@ -17,7 +17,7 @@ class SearchResultsSortButton extends StatefulWidget {
class _State extends State<SearchResultsSortButton> {
@override
Widget build(BuildContext context) => LSCard(
child: Consumer<SearchModel>(
child: Consumer<SearchState>(
builder: (context, model, widget) => PopupMenuButton<SearchResultsSorting>(
shape: LunaSeaDatabaseValue.THEME_AMOLED.data && LunaSeaDatabaseValue.THEME_AMOLED_BORDER.data
? LSRoundedShapeWithBorder()
@@ -50,7 +50,7 @@ class _State extends State<SearchResultsSortButton> {
? Icons.arrow_upward
: Icons.arrow_downward,
size: Constants.UI_FONT_SIZE_SUBTITLE+2.0,
color: LSColors.accent,
color: LunaColours.accent,
),
],
),

View File

@@ -19,13 +19,13 @@ class _State extends State<SearchSearchBar> {
@override
void initState() {
super.initState();
final model = Provider.of<SearchModel>(context, listen: false);
final model = Provider.of<SearchState>(context, listen: false);
_controller.text = model.searchQuery;
}
@override
Widget build(BuildContext context) => Expanded(
child: Consumer<SearchModel>(
child: Consumer<SearchState>(
builder: (context, model, _) => LSTextInputBar(
controller: _controller,
onChanged: (text, updateController) => _onChange(model, text, updateController),
@@ -35,8 +35,8 @@ class _State extends State<SearchSearchBar> {
),
);
void _onChange(SearchModel model, String text, updateController) {
model?.searchQuery = text;
void _onChange(SearchState state, String text, updateController) {
state?.searchQuery = text;
if(updateController) _controller.text = text;
}

View File

@@ -22,7 +22,7 @@ class SearchSubcategoryTile extends StatelessWidget {
title: LSTitle(text: category?.subcategories[index ?? 0]?.name ?? 'Unknown'),
subtitle: LSSubtitle(text: '${category?.name ?? 'Unknown'} > ${category?.subcategories[index ?? 0]?.name ?? 'Unknown'}'),
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
leading: LSIconButton(icon: category?.icon, color: LSColors.list(index+1)),
leading: LSIconButton(icon: category?.icon, color: LunaColours.list(index+1)),
onTap: () => _enterResults(
context,
category?.subcategories[index ?? 0]?.id ?? 0,
@@ -33,7 +33,7 @@ class SearchSubcategoryTile extends StatelessWidget {
Widget _cardAll(BuildContext context) => LSCardTile(
title: LSTitle(text: 'All Subcategories'),
subtitle: LSSubtitle(text: '${category?.name} > All'),
leading: LSIconButton(icon: category?.icon, color: LSColors.list(0)),
leading: LSIconButton(icon: category?.icon, color: LunaColours.list(0)),
trailing: LSIconButton(icon: Icons.arrow_forward_ios),
onTap: () async => _enterResults(
context,
@@ -43,7 +43,7 @@ class SearchSubcategoryTile extends StatelessWidget {
);
Future<void> _enterResults(BuildContext context, int id, String title) async {
final model = Provider.of<SearchModel>(context, listen: false);
final model = Provider.of<SearchState>(context, listen: false);
model.searchTitle = title;
model.searchCategoryID = id;
Navigator.of(context).pushNamed(SearchResults.ROUTE_NAME);