mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-03-11 21:35:32 +00:00
feat: search filter feature
This commit is contained in:
parent
8440b25935
commit
09b002be8b
12 changed files with 1536 additions and 39 deletions
1034
lib/eval/bridge/filter.dart
Normal file
1034
lib/eval/bridge/filter.dart
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -2,12 +2,14 @@ import 'dart:convert';
|
|||
import 'package:dart_eval/dart_eval.dart';
|
||||
import 'package:dart_eval/dart_eval_bridge.dart';
|
||||
import 'package:dart_eval/stdlib/core.dart';
|
||||
import 'package:mangayomi/eval/bridge/filter.dart';
|
||||
import 'package:mangayomi/eval/bridge/m_source.dart';
|
||||
import 'package:mangayomi/eval/bridge/m_manga.dart';
|
||||
import 'package:mangayomi/eval/bridge/m_pages.dart';
|
||||
import 'package:mangayomi/eval/bridge/m_status.dart';
|
||||
import 'package:mangayomi/eval/bridge/m_track.dart';
|
||||
import 'package:mangayomi/eval/bridge/m_video.dart';
|
||||
import 'package:mangayomi/eval/model/filter.dart';
|
||||
import 'package:mangayomi/eval/model/m_bridge.dart';
|
||||
import 'package:mangayomi/eval/model/m_pages.dart';
|
||||
import 'package:mangayomi/eval/model/m_source.dart';
|
||||
|
|
@ -127,6 +129,9 @@ class $MProvider extends MProvider with $Bridge<MProvider> {
|
|||
false,
|
||||
),
|
||||
])),
|
||||
'getFilterList': BridgeMethodDef(BridgeFunctionDef(
|
||||
returns: BridgeTypeAnnotation(BridgeTypeRef(
|
||||
CoreTypes.future, [BridgeTypeRef(CoreTypes.dynamic)])))),
|
||||
'cryptoHandler': BridgeMethodDef(
|
||||
BridgeFunctionDef(
|
||||
returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)),
|
||||
|
|
@ -963,9 +968,14 @@ class $MProvider extends MProvider with $Bridge<MProvider> {
|
|||
await $_invoke('getPopular', [$MSource.wrap(source), $int(page)]);
|
||||
|
||||
@override
|
||||
Future<MPages> search(MSource source, String query, int page) async =>
|
||||
await $_invoke(
|
||||
'search', [$MSource.wrap(source), $String(query), $int(page)]);
|
||||
Future<MPages> search(MSource source, String query, int page,
|
||||
FilterList filterList) async =>
|
||||
await $_invoke('search', [
|
||||
$MSource.wrap(source),
|
||||
$String(query),
|
||||
$int(page),
|
||||
$FilterList.wrap(FilterList(_toValueList(filterList.filters)))
|
||||
]);
|
||||
|
||||
@override
|
||||
Future<List<String>> getPageList(MSource source, String url) async {
|
||||
|
|
@ -977,6 +987,15 @@ class $MProvider extends MProvider with $Bridge<MProvider> {
|
|||
return res;
|
||||
}
|
||||
|
||||
@override
|
||||
List getFilterList() {
|
||||
final res = $_invoke('getFilterList', []);
|
||||
if (res is $List) {
|
||||
return res.$reified;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Video>> getVideoList(MSource source, String url) async {
|
||||
final res =
|
||||
|
|
@ -1000,4 +1019,35 @@ class $MProvider extends MProvider with $Bridge<MProvider> {
|
|||
: e.audios!
|
||||
.map((t) => $MTrack.wrap(Track(file: t.file, label: t.label)))
|
||||
.toList()));
|
||||
_toValueList(List filters) {
|
||||
return (filters).map((e) {
|
||||
if (e is SelectFilter) {
|
||||
return $SelectFilter
|
||||
.wrap(SelectFilter(e.type, e.name, e.state, _toValueList(e.values)));
|
||||
} else if (e is TextFilter) {
|
||||
return $TextFilter.wrap(e);
|
||||
} else if (e is HeaderFilter) {
|
||||
return $HeaderFilter.wrap(e);
|
||||
} else if (e is CheckBoxFilter) {
|
||||
return $CheckBoxFilter.wrap(e);
|
||||
} else if (e is TriStateFilter) {
|
||||
return $TriStateFilter.wrap(e);
|
||||
} else if (e is SeparatorFilter) {
|
||||
return $SeparatorFilter.wrap(e);
|
||||
} else if (e is SortFilter) {
|
||||
return $SortFilter
|
||||
.wrap(SortFilter(e.type, e.name, e.state, _toValueList(e.values)));
|
||||
} else if (e is SortState) {
|
||||
return $SortState.wrap(e);
|
||||
} else if (e is CheckBoxFilter) {
|
||||
return $CheckBoxFilter.wrap(e);
|
||||
} else if (e is SelectFilterOption) {
|
||||
return $SelectFilterOption.wrap(e);
|
||||
} else if (e is GroupFilter) {
|
||||
return $GroupFilter
|
||||
.wrap(GroupFilter(e.type, e.name, _toValueList(e.state)));
|
||||
}
|
||||
return e;
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
92
lib/eval/model/filter.dart
Normal file
92
lib/eval/model/filter.dart
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
class FilterList {
|
||||
List<dynamic> filters;
|
||||
FilterList(this.filters);
|
||||
}
|
||||
|
||||
class SelectFilter {
|
||||
String? type;
|
||||
String name;
|
||||
int state;
|
||||
List<dynamic> values;
|
||||
|
||||
SelectFilter(this.type, this.name, this.state, this.values);
|
||||
}
|
||||
|
||||
class SelectFilterOption {
|
||||
String name;
|
||||
String value;
|
||||
|
||||
SelectFilterOption(this.name, this.value);
|
||||
}
|
||||
|
||||
class SeparatorFilter {
|
||||
String? type;
|
||||
SeparatorFilter({this.type = ''});
|
||||
}
|
||||
|
||||
class HeaderFilter {
|
||||
String? type;
|
||||
String name;
|
||||
HeaderFilter(this.name, {this.type = ''});
|
||||
}
|
||||
|
||||
class TextFilter {
|
||||
String? type;
|
||||
String name;
|
||||
String state;
|
||||
|
||||
TextFilter(this.type, this.name, {this.state = ""});
|
||||
}
|
||||
|
||||
class SortFilter {
|
||||
String? type;
|
||||
String name;
|
||||
SortState state;
|
||||
List<dynamic> values;
|
||||
|
||||
SortFilter(this.type, this.name, this.state, this.values);
|
||||
}
|
||||
|
||||
class SortState {
|
||||
int index;
|
||||
bool ascending;
|
||||
|
||||
SortState(this.index, this.ascending);
|
||||
}
|
||||
|
||||
class TriStateFilter {
|
||||
String? type;
|
||||
String name;
|
||||
String value;
|
||||
int state;
|
||||
|
||||
factory TriStateFilter.fromJson(Map<String, dynamic> json) {
|
||||
return TriStateFilter(json['type'], json['name'], json['value']);
|
||||
}
|
||||
TriStateFilter(this.type, this.name, this.value, {this.state = 0});
|
||||
}
|
||||
|
||||
extension TriStateFilterExtension on TriStateFilter {
|
||||
bool get ignored => state == 0;
|
||||
bool get included => state == 1;
|
||||
bool get excluded => state == 2;
|
||||
}
|
||||
|
||||
class GroupFilter {
|
||||
String? type;
|
||||
String name;
|
||||
List<dynamic> state;
|
||||
|
||||
GroupFilter(this.type, this.name, this.state);
|
||||
}
|
||||
|
||||
class CheckBoxFilter {
|
||||
String? type;
|
||||
String name;
|
||||
String value;
|
||||
bool state;
|
||||
factory CheckBoxFilter.fromJson(Map<String, dynamic> json) {
|
||||
return CheckBoxFilter(json['type'], json['name'], json['value']);
|
||||
}
|
||||
CheckBoxFilter(this.type, this.name, this.value, {this.state = false});
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:mangayomi/eval/model/filter.dart';
|
||||
import 'package:mangayomi/eval/model/m_pages.dart';
|
||||
import 'package:mangayomi/eval/model/m_source.dart';
|
||||
import 'package:mangayomi/eval/model/m_manga.dart';
|
||||
|
|
@ -10,7 +11,8 @@ abstract class MProvider {
|
|||
|
||||
Future<MPages> getPopular(MSource source, int page);
|
||||
|
||||
Future<MPages> search(MSource source, String query, int page);
|
||||
Future<MPages> search(
|
||||
MSource source, String query, int page, FilterList filterList);
|
||||
|
||||
Future<MManga> getDetail(MSource source, String url);
|
||||
|
||||
|
|
@ -18,5 +20,5 @@ abstract class MProvider {
|
|||
|
||||
Future<List<Video>> getVideoList(MSource source, String url);
|
||||
|
||||
// FilterList getFilterList();
|
||||
List<dynamic> getFilterList();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:dart_eval/dart_eval.dart';
|
||||
import 'package:dart_eval/dart_eval_bridge.dart';
|
||||
import 'package:mangayomi/eval/bridge/m_chapter.dart';
|
||||
import 'package:mangayomi/eval/bridge/filter.dart';
|
||||
import 'package:mangayomi/eval/bridge/m_pages.dart';
|
||||
import 'package:mangayomi/eval/bridge/m_status.dart';
|
||||
import 'package:mangayomi/eval/bridge/m_provider.dart';
|
||||
|
|
@ -23,6 +24,18 @@ class MEvalPlugin extends EvalPlugin {
|
|||
registry.defineBridgeClass($MChapter.$declaration);
|
||||
registry.defineBridgeClass($MManga.$declaration);
|
||||
registry.defineBridgeEnum($MStatus.$declaration);
|
||||
//Filter
|
||||
registry.defineBridgeClass($FilterList.$declaration);
|
||||
registry.defineBridgeClass($SelectFilter.$declaration);
|
||||
registry.defineBridgeClass($SeparatorFilter.$declaration);
|
||||
registry.defineBridgeClass($HeaderFilter.$declaration);
|
||||
registry.defineBridgeClass($TextFilter.$declaration);
|
||||
registry.defineBridgeClass($SortFilter.$declaration);
|
||||
registry.defineBridgeClass($TriStateFilter.$declaration);
|
||||
registry.defineBridgeClass($GroupFilter.$declaration);
|
||||
registry.defineBridgeClass($CheckBoxFilter.$declaration);
|
||||
registry.defineBridgeClass($SortState.$declaration);
|
||||
registry.defineBridgeClass($SelectFilterOption.$declaration);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -44,5 +57,28 @@ class MEvalPlugin extends EvalPlugin {
|
|||
'package:mangayomi/bridge_lib.dart', 'MTrack.', $MTrack.$new);
|
||||
runtime.registerBridgeEnumValues(
|
||||
'package:mangayomi/bridge_lib.dart', 'MStatus', $MStatus.$values);
|
||||
//Filter
|
||||
runtime.registerBridgeFunc(
|
||||
'package:mangayomi/bridge_lib.dart', 'FilterList.', $FilterList.$new);
|
||||
runtime.registerBridgeFunc('package:mangayomi/bridge_lib.dart',
|
||||
'SelectFilter.', $SelectFilter.$new);
|
||||
runtime.registerBridgeFunc('package:mangayomi/bridge_lib.dart',
|
||||
'SeparatorFilter.', $SeparatorFilter.$new);
|
||||
runtime.registerBridgeFunc('package:mangayomi/bridge_lib.dart',
|
||||
'HeaderFilter.', $HeaderFilter.$new);
|
||||
runtime.registerBridgeFunc(
|
||||
'package:mangayomi/bridge_lib.dart', 'TextFilter.', $TextFilter.$new);
|
||||
runtime.registerBridgeFunc(
|
||||
'package:mangayomi/bridge_lib.dart', 'SortFilter.', $SortFilter.$new);
|
||||
runtime.registerBridgeFunc('package:mangayomi/bridge_lib.dart',
|
||||
'TriStateFilter.', $TriStateFilter.$new);
|
||||
runtime.registerBridgeFunc(
|
||||
'package:mangayomi/bridge_lib.dart', 'GroupFilter.', $GroupFilter.$new);
|
||||
runtime.registerBridgeFunc('package:mangayomi/bridge_lib.dart',
|
||||
'CheckBoxFilter.', $CheckBoxFilter.$new);
|
||||
runtime.registerBridgeFunc(
|
||||
'package:mangayomi/bridge_lib.dart', 'SortState.', $SortState.$new);
|
||||
runtime.registerBridgeFunc('package:mangayomi/bridge_lib.dart',
|
||||
'SelectFilterOption.', $SelectFilterOption.$new);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,11 +112,8 @@ class SourceSearchScreen extends ConsumerWidget {
|
|||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
final search = ref.watch(searchProvider(
|
||||
source: source,
|
||||
page: 1,
|
||||
query: query,
|
||||
));
|
||||
final search = ref.watch(
|
||||
searchProvider(source: source, page: 1, query: query, filterList: []));
|
||||
return Scaffold(
|
||||
body: SizedBox(
|
||||
height: 260,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ import 'package:go_router/go_router.dart';
|
|||
import 'package:mangayomi/eval/model/m_manga.dart';
|
||||
import 'package:mangayomi/eval/model/m_pages.dart';
|
||||
import 'package:mangayomi/models/source.dart';
|
||||
import 'package:mangayomi/modules/manga/home/widget/filter_widget.dart';
|
||||
import 'package:mangayomi/modules/widgets/progress_center.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/services/get_filter_list.dart';
|
||||
import 'package:mangayomi/services/get_latest_updates.dart';
|
||||
import 'package:mangayomi/services/get_popular.dart';
|
||||
import 'package:mangayomi/services/search.dart';
|
||||
|
|
@ -45,6 +48,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
int _fullDataLength = 50;
|
||||
int _page = 1;
|
||||
late int _selectedIndex = widget.isSearch ? 2 : 0;
|
||||
List<dynamic> filters = [];
|
||||
List<TypeMangaSelector> _types(BuildContext context) {
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
return [
|
||||
|
|
@ -72,12 +76,15 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
source: widget.source,
|
||||
page: _page + 1,
|
||||
).future);
|
||||
} else if (_selectedIndex == 2 && _isSearch && _query.isNotEmpty) {
|
||||
} else if (_selectedIndex == 2 &&
|
||||
_isSearch &&
|
||||
(_query.isNotEmpty || _isFiltering)) {
|
||||
mangaResList = await ref.watch(searchProvider(
|
||||
source: widget.source,
|
||||
query: _query,
|
||||
page: _page + 1,
|
||||
).future);
|
||||
source: widget.source,
|
||||
query: _query,
|
||||
page: _page + 1,
|
||||
filterList: filters)
|
||||
.future);
|
||||
}
|
||||
}
|
||||
if (mounted) {
|
||||
|
|
@ -94,11 +101,15 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
late bool _isSearch = widget.isSearch;
|
||||
AsyncValue<MPages?>? _getManga;
|
||||
int _length = 0;
|
||||
bool _isFiltering = false;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_selectedIndex == 2 && _isSearch && _query.isNotEmpty) {
|
||||
_getManga = ref
|
||||
.watch(searchProvider(source: widget.source, query: _query, page: 1));
|
||||
final filterList = getFilterList(source: widget.source);
|
||||
if (_selectedIndex == 2 &&
|
||||
_isSearch &&
|
||||
(_query.isNotEmpty || _isFiltering)) {
|
||||
_getManga = ref.watch(searchProvider(
|
||||
source: widget.source, query: _query, page: 1, filterList: filters));
|
||||
} else if (_selectedIndex == 1 && !_isSearch && _query.isEmpty) {
|
||||
_getManga =
|
||||
ref.watch(getLatestUpdatesProvider(source: widget.source, page: 1));
|
||||
|
|
@ -138,6 +149,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
if (_textEditingController.text.isEmpty) {
|
||||
_isSearch = false;
|
||||
_query = "";
|
||||
_isFiltering = false;
|
||||
_selectedIndex = 0;
|
||||
_page = 1;
|
||||
_textEditingController.clear();
|
||||
|
|
@ -189,11 +201,75 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
icon: _types(context)[index].icon,
|
||||
selected: _selectedIndex == index,
|
||||
text: _types(context)[index].title,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_selectedIndex = index;
|
||||
_page = 1;
|
||||
});
|
||||
onPressed: () async {
|
||||
if (filters.isEmpty) {
|
||||
filters = filterList;
|
||||
}
|
||||
if (filters.isNotEmpty && index == 2) {
|
||||
final result = await showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
StatefulBuilder(builder: (context, setState) {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
filters = getFilterList(
|
||||
source: widget.source);
|
||||
});
|
||||
},
|
||||
child: Text(l10n.reset),
|
||||
),
|
||||
const Spacer(),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context, 'filter');
|
||||
},
|
||||
child: Text(l10n.filter),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
Expanded(
|
||||
child: FilterWidget(
|
||||
filterList: filters,
|
||||
onChanged: (values) {
|
||||
setState(() {
|
||||
filters = values;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
if (result == 'filter') {
|
||||
setState(() {
|
||||
_selectedIndex = 2;
|
||||
_isFiltering = true;
|
||||
_isSearch = true;
|
||||
_page = 1;
|
||||
});
|
||||
_getManga = ref.refresh(searchProvider(
|
||||
source: widget.source,
|
||||
query: _query,
|
||||
page: 1,
|
||||
filterList: filters));
|
||||
}
|
||||
} else if (index != 2) {
|
||||
setState(() {
|
||||
_selectedIndex = index;
|
||||
_isFiltering = false;
|
||||
_page = 1;
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
|
|
@ -215,6 +291,9 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
},
|
||||
child: _getManga!.when(
|
||||
data: (data) {
|
||||
if (_getManga!.isLoading) {
|
||||
return const ProgressCenter();
|
||||
}
|
||||
Widget buildProgressIndicator() {
|
||||
return !(data!.list.isNotEmpty && (data.hasNextPage))
|
||||
? Container()
|
||||
|
|
@ -334,11 +413,12 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
onPressed: () {
|
||||
if (_selectedIndex == 2 &&
|
||||
_isSearch &&
|
||||
_query.isNotEmpty) {
|
||||
(_query.isNotEmpty || _isFiltering)) {
|
||||
ref.invalidate(searchProvider(
|
||||
source: widget.source,
|
||||
query: _query,
|
||||
page: 1));
|
||||
page: 1,
|
||||
filterList: filters));
|
||||
} else if (_selectedIndex == 1 &&
|
||||
!_isSearch &&
|
||||
_query.isEmpty) {
|
||||
|
|
|
|||
163
lib/modules/manga/home/widget/filter_widget.dart
Normal file
163
lib/modules/manga/home/widget/filter_widget.dart
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:mangayomi/eval/model/filter.dart';
|
||||
import 'package:mangayomi/utils/colors.dart';
|
||||
|
||||
class FilterWidget extends StatelessWidget {
|
||||
final List<dynamic> filterList;
|
||||
final Function(List<dynamic>) onChanged;
|
||||
const FilterWidget(
|
||||
{super.key, required this.onChanged, required this.filterList});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.all(0),
|
||||
itemCount: filterList.length,
|
||||
primary: false,
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, idx) {
|
||||
final filterState = filterList[idx];
|
||||
Widget? widget;
|
||||
if (filterState is TextFilter) {
|
||||
widget = Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: TextField(
|
||||
onChanged: (val) {
|
||||
filterList[idx] = filterState..state = val;
|
||||
onChanged(filterList);
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: secondaryColor(context)),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: primaryColor(context)),
|
||||
),
|
||||
border: const OutlineInputBorder(borderSide: BorderSide()),
|
||||
labelText: filterState.name,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else if (filterState is HeaderFilter) {
|
||||
widget = ListTile(dense: true, title: Text(filterState.name));
|
||||
} else if (filterState is SeparatorFilter) {
|
||||
widget = const Divider();
|
||||
} else if (filterState is TriStateFilter) {
|
||||
final state = filterState.state;
|
||||
widget = CheckboxListTile(
|
||||
dense: true,
|
||||
value: state == 0
|
||||
? false
|
||||
: state == 1
|
||||
? true
|
||||
: null,
|
||||
onChanged: (value) {
|
||||
filterList[idx] = filterState
|
||||
..state = value == null
|
||||
? 2
|
||||
: value == true
|
||||
? 1
|
||||
: 0;
|
||||
onChanged(filterList);
|
||||
},
|
||||
title: Text(filterState.name),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
tristate: true,
|
||||
);
|
||||
} else if (filterState is CheckBoxFilter) {
|
||||
widget = CheckboxListTile(
|
||||
dense: true,
|
||||
value: filterState.state,
|
||||
onChanged: (value) {
|
||||
filterList[idx] = filterState..state = value!;
|
||||
onChanged(filterList);
|
||||
},
|
||||
title: Text(filterState.name),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
);
|
||||
} else if (filterState is GroupFilter) {
|
||||
widget = ExpansionTile(
|
||||
title: Text(filterState.name, style: const TextStyle(fontSize: 13)),
|
||||
children: [
|
||||
FilterWidget(
|
||||
filterList: filterState.state,
|
||||
onChanged: (values) {
|
||||
filterState.state = values;
|
||||
onChanged(filterList);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
} else if (filterState is SortFilter) {
|
||||
final ascending = filterState.state.ascending;
|
||||
widget = ExpansionTile(
|
||||
title: Text(filterState.name, style: const TextStyle(fontSize: 13)),
|
||||
children: filterState.values.map((e) {
|
||||
final selected = filterState.values[filterState.state.index] == e;
|
||||
return ListTile(
|
||||
dense: true,
|
||||
leading: Icon(
|
||||
ascending
|
||||
? Icons.arrow_upward_rounded
|
||||
: Icons.arrow_downward_rounded,
|
||||
color: selected ? null : Colors.transparent),
|
||||
title: Text(e.name),
|
||||
onTap: () {
|
||||
if (selected) {
|
||||
filterState.state.ascending = !ascending;
|
||||
} else {
|
||||
filterState.state.index = filterState.values
|
||||
.indexWhere((element) => element == e);
|
||||
}
|
||||
filterList[idx] = filterState;
|
||||
onChanged(filterList);
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
} else if (filterState is SelectFilter) {
|
||||
widget = Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListTile(
|
||||
dense: true,
|
||||
title: Text(filterState.name),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 8, horizontal: 25),
|
||||
child: DropdownButton(
|
||||
icon: const Icon(Icons.keyboard_arrow_down),
|
||||
isExpanded: true,
|
||||
value: filterState.values[filterState.state],
|
||||
hint: Text(filterState.name,
|
||||
style: const TextStyle(fontSize: 13)),
|
||||
items: filterState.values
|
||||
.map((e) => DropdownMenuItem(
|
||||
value: e,
|
||||
child: Text(e.name,
|
||||
style: const TextStyle(fontSize: 13)),
|
||||
))
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
filterState.state = filterState.values
|
||||
.indexWhere((element) => element == value);
|
||||
onChanged(filterList);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return widget ?? const SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,11 +6,11 @@ part of 'auto_backup.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$checkAndBackupHash() => r'039aea77df04d1925a25ad8839177cf9ee96aa65';
|
||||
String _$checkAndBackupHash() => r'fad714bbc0d37442d4f72fd98d91fcaf72166284';
|
||||
|
||||
/// See also [checkAndBackup].
|
||||
@ProviderFor(checkAndBackup)
|
||||
final checkAndBackupProvider = AutoDisposeProvider<void>.internal(
|
||||
final checkAndBackupProvider = AutoDisposeFutureProvider<void>.internal(
|
||||
checkAndBackup,
|
||||
name: r'checkAndBackupProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
|
|
@ -20,7 +20,7 @@ final checkAndBackupProvider = AutoDisposeProvider<void>.internal(
|
|||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef CheckAndBackupRef = AutoDisposeProviderRef<void>;
|
||||
typedef CheckAndBackupRef = AutoDisposeFutureProviderRef<void>;
|
||||
String _$backupFrequencyStateHash() =>
|
||||
r'2e73e3fe54456978ff92f49cdc67e84f2af6de7c';
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ final backupFrequencyStateProvider =
|
|||
|
||||
typedef _$BackupFrequencyState = AutoDisposeNotifier<int>;
|
||||
String _$backupFrequencyOptionsStateHash() =>
|
||||
r'98c9a7014b56b3a2ec243f82efa3c2b5aade03fc';
|
||||
r'79d93411a02867c8882d2d0f2143f5da6c107075';
|
||||
|
||||
/// See also [BackupFrequencyOptionsState].
|
||||
@ProviderFor(BackupFrequencyOptionsState)
|
||||
|
|
@ -56,7 +56,7 @@ final backupFrequencyOptionsStateProvider = AutoDisposeNotifierProvider<
|
|||
|
||||
typedef _$BackupFrequencyOptionsState = AutoDisposeNotifier<List<int>>;
|
||||
String _$autoBackupLocationStateHash() =>
|
||||
r'0ec954361736e570e3bc6fa4e7895fc79070cb33';
|
||||
r'87feb5475d47a77da4e87fa62e551b785fa1e7ba';
|
||||
|
||||
/// See also [AutoBackupLocationState].
|
||||
@ProviderFor(AutoBackupLocationState)
|
||||
|
|
|
|||
27
lib/services/get_filter_list.dart
Normal file
27
lib/services/get_filter_list.dart
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import 'package:dart_eval/dart_eval_bridge.dart';
|
||||
import 'package:mangayomi/eval/compiler/compiler.dart';
|
||||
import 'package:mangayomi/eval/model/m_provider.dart';
|
||||
import 'package:mangayomi/models/source.dart';
|
||||
import 'package:mangayomi/eval/runtime/runtime.dart';
|
||||
import 'package:mangayomi/sources/source_test.dart';
|
||||
|
||||
List<dynamic> getFilterList({required Source source}) {
|
||||
List<dynamic> filterList = [];
|
||||
|
||||
try {
|
||||
final bytecode =
|
||||
compilerEval(useTestSourceCode ? testSourceCode : source.sourceCode!);
|
||||
|
||||
final runtime = runtimeEval(bytecode);
|
||||
|
||||
var res = runtime.executeLib('package:mangayomi/main.dart', 'main');
|
||||
filterList = (res as MProvider)
|
||||
.getFilterList()
|
||||
.map((e) => e is $Value ? e.$reified : e)
|
||||
.toList();
|
||||
} catch (_) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return filterList;
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:mangayomi/eval/model/filter.dart';
|
||||
import 'package:mangayomi/eval/model/m_pages.dart';
|
||||
import 'package:mangayomi/eval/compiler/compiler.dart';
|
||||
import 'package:mangayomi/eval/model/m_provider.dart';
|
||||
|
|
@ -8,12 +9,11 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|||
part 'search.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<MPages?> search(
|
||||
SearchRef ref, {
|
||||
required Source source,
|
||||
required String query,
|
||||
required int page,
|
||||
}) async {
|
||||
Future<MPages?> search(SearchRef ref,
|
||||
{required Source source,
|
||||
required String query,
|
||||
required int page,
|
||||
required List<dynamic> filterList}) async {
|
||||
MPages? manga;
|
||||
final bytecode =
|
||||
compilerEval(useTestSourceCode ? testSourceCode : source.sourceCode!);
|
||||
|
|
@ -21,7 +21,7 @@ Future<MPages?> search(
|
|||
var res = runtime.executeLib('package:mangayomi/main.dart', 'main');
|
||||
try {
|
||||
manga = await (res as MProvider)
|
||||
.search(source.toMSource(), query, page);
|
||||
.search(source.toMSource(), query, page, FilterList(filterList));
|
||||
} catch (e) {
|
||||
throw Exception(e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'search.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$searchHash() => r'a387d6ddc86044bba9c8264c8bec6c1c29393ac5';
|
||||
String _$searchHash() => r'b2683242cc45e83f3946339d3490cad6488dd847';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
@ -43,11 +43,13 @@ class SearchFamily extends Family<AsyncValue<MPages?>> {
|
|||
required Source source,
|
||||
required String query,
|
||||
required int page,
|
||||
required List<dynamic> filterList,
|
||||
}) {
|
||||
return SearchProvider(
|
||||
source: source,
|
||||
query: query,
|
||||
page: page,
|
||||
filterList: filterList,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -59,6 +61,7 @@ class SearchFamily extends Family<AsyncValue<MPages?>> {
|
|||
source: provider.source,
|
||||
query: provider.query,
|
||||
page: provider.page,
|
||||
filterList: provider.filterList,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -84,12 +87,14 @@ class SearchProvider extends AutoDisposeFutureProvider<MPages?> {
|
|||
required Source source,
|
||||
required String query,
|
||||
required int page,
|
||||
required List<dynamic> filterList,
|
||||
}) : this._internal(
|
||||
(ref) => search(
|
||||
ref as SearchRef,
|
||||
source: source,
|
||||
query: query,
|
||||
page: page,
|
||||
filterList: filterList,
|
||||
),
|
||||
from: searchProvider,
|
||||
name: r'searchProvider',
|
||||
|
|
@ -102,6 +107,7 @@ class SearchProvider extends AutoDisposeFutureProvider<MPages?> {
|
|||
source: source,
|
||||
query: query,
|
||||
page: page,
|
||||
filterList: filterList,
|
||||
);
|
||||
|
||||
SearchProvider._internal(
|
||||
|
|
@ -114,11 +120,13 @@ class SearchProvider extends AutoDisposeFutureProvider<MPages?> {
|
|||
required this.source,
|
||||
required this.query,
|
||||
required this.page,
|
||||
required this.filterList,
|
||||
}) : super.internal();
|
||||
|
||||
final Source source;
|
||||
final String query;
|
||||
final int page;
|
||||
final List<dynamic> filterList;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
|
|
@ -136,6 +144,7 @@ class SearchProvider extends AutoDisposeFutureProvider<MPages?> {
|
|||
source: source,
|
||||
query: query,
|
||||
page: page,
|
||||
filterList: filterList,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -150,7 +159,8 @@ class SearchProvider extends AutoDisposeFutureProvider<MPages?> {
|
|||
return other is SearchProvider &&
|
||||
other.source == source &&
|
||||
other.query == query &&
|
||||
other.page == page;
|
||||
other.page == page &&
|
||||
other.filterList == filterList;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -159,6 +169,7 @@ class SearchProvider extends AutoDisposeFutureProvider<MPages?> {
|
|||
hash = _SystemHash.combine(hash, source.hashCode);
|
||||
hash = _SystemHash.combine(hash, query.hashCode);
|
||||
hash = _SystemHash.combine(hash, page.hashCode);
|
||||
hash = _SystemHash.combine(hash, filterList.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
|
|
@ -173,6 +184,9 @@ mixin SearchRef on AutoDisposeFutureProviderRef<MPages?> {
|
|||
|
||||
/// The parameter `page` of this provider.
|
||||
int get page;
|
||||
|
||||
/// The parameter `filterList` of this provider.
|
||||
List<dynamic> get filterList;
|
||||
}
|
||||
|
||||
class _SearchProviderElement extends AutoDisposeFutureProviderElement<MPages?>
|
||||
|
|
@ -185,6 +199,8 @@ class _SearchProviderElement extends AutoDisposeFutureProviderElement<MPages?>
|
|||
String get query => (origin as SearchProvider).query;
|
||||
@override
|
||||
int get page => (origin as SearchProvider).page;
|
||||
@override
|
||||
List<dynamic> get filterList => (origin as SearchProvider).filterList;
|
||||
}
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
|
|
|
|||
Loading…
Reference in a new issue