search options in results page

This commit is contained in:
nyne
2024-10-18 11:34:31 +08:00
parent e251355d66
commit 30b3256eec
4 changed files with 178 additions and 24 deletions

View File

@@ -184,12 +184,12 @@ class _MySliverAppBarDelegate extends SliverPersistentHeaderDelegate {
leading ?? leading ??
(Navigator.of(context).canPop() (Navigator.of(context).canPop()
? Tooltip( ? Tooltip(
message: "Back".tl, message: "Back".tl,
child: IconButton( child: IconButton(
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.maybePop(context), onPressed: () => Navigator.maybePop(context),
), ),
) )
: const SizedBox()), : const SizedBox()),
const SizedBox( const SizedBox(
width: 24, width: 24,
@@ -197,7 +197,7 @@ class _MySliverAppBarDelegate extends SliverPersistentHeaderDelegate {
Expanded( Expanded(
child: DefaultTextStyle( child: DefaultTextStyle(
style: style:
DefaultTextStyle.of(context).style.copyWith(fontSize: 20), DefaultTextStyle.of(context).style.copyWith(fontSize: 20),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
child: title, child: title,
@@ -562,12 +562,19 @@ abstract mixin class _SearchBarMixin {
} }
class SliverSearchBar extends StatefulWidget { class SliverSearchBar extends StatefulWidget {
const SliverSearchBar({super.key, required this.controller, this.onChanged}); const SliverSearchBar({
super.key,
required this.controller,
this.onChanged,
this.action,
});
final SearchBarController controller; final SearchBarController controller;
final void Function(String)? onChanged; final void Function(String)? onChanged;
final Widget? action;
@override @override
State<SliverSearchBar> createState() => _SliverSearchBarState(); State<SliverSearchBar> createState() => _SliverSearchBarState();
} }
@@ -605,6 +612,7 @@ class _SliverSearchBarState extends State<SliverSearchBar>
controller: _controller, controller: _controller,
topPadding: MediaQuery.of(context).padding.top, topPadding: MediaQuery.of(context).padding.top,
onChanged: widget.onChanged, onChanged: widget.onChanged,
action: widget.action,
), ),
); );
} }
@@ -619,11 +627,14 @@ class _SliverSearchBarDelegate extends SliverPersistentHeaderDelegate {
final void Function(String)? onChanged; final void Function(String)? onChanged;
final Widget? action;
const _SliverSearchBarDelegate({ const _SliverSearchBarDelegate({
required this.editingController, required this.editingController,
required this.controller, required this.controller,
required this.topPadding, required this.topPadding,
this.onChanged, this.onChanged,
this.action,
}); });
static const _kAppBarHeight = 52.0; static const _kAppBarHeight = 52.0;
@@ -677,6 +688,7 @@ class _SliverSearchBarDelegate extends SliverPersistentHeaderDelegate {
); );
}, },
), ),
if (action != null) action!,
const SizedBox(width: 8), const SizedBox(width: 8),
], ],
), ),
@@ -699,10 +711,12 @@ class _SliverSearchBarDelegate extends SliverPersistentHeaderDelegate {
} }
class AppSearchBar extends StatefulWidget { class AppSearchBar extends StatefulWidget {
const AppSearchBar({super.key, required this.controller}); const AppSearchBar({super.key, required this.controller, this.action});
final SearchBarController controller; final SearchBarController controller;
final Widget? action;
@override @override
State<AppSearchBar> createState() => _SearchBarState(); State<AppSearchBar> createState() => _SearchBarState();
} }
@@ -777,6 +791,7 @@ class _SearchBarState extends State<AppSearchBar> with _SearchBarMixin {
); );
}, },
), ),
if (widget.action != null) widget.action!,
const SizedBox(width: 8), const SizedBox(width: 8),
], ],
), ),

View File

@@ -277,17 +277,21 @@ class ContentDialog extends StatelessWidget {
: const EdgeInsets.symmetric(horizontal: 16), : const EdgeInsets.symmetric(horizontal: 16),
elevation: 2, elevation: 2,
shadowColor: context.colorScheme.shadow, shadowColor: context.colorScheme.shadow,
child: IntrinsicWidth( child: AnimatedSize(
child: ConstrainedBox( duration: const Duration(milliseconds: 200),
constraints: BoxConstraints( alignment: Alignment.topCenter,
maxWidth: 600, child: IntrinsicWidth(
minWidth: math.min(400, context.width - 16), child: ConstrainedBox(
), constraints: BoxConstraints(
child: MediaQuery.removePadding( maxWidth: 600,
removeTop: true, minWidth: math.min(400, context.width - 16),
removeBottom: true, ),
context: context, child: MediaQuery.removePadding(
child: content, removeTop: true,
removeBottom: true,
context: context,
child: content,
),
), ),
), ),
), ),

View File

@@ -174,7 +174,7 @@ class _SearchPageState extends State<SearchPage> {
children: [ children: [
ListTile( ListTile(
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
title: Text("Search From".tl), title: Text("Search in".tl),
), ),
Wrap( Wrap(
spacing: 8, spacing: 8,

View File

@@ -49,7 +49,7 @@ class _SearchResultPageState extends State<SearchResultPage> {
} }
void onChanged(String s) { void onChanged(String s) {
if(!ComicSource.find(sourceKey)!.enableTagsSuggestions){ if (!ComicSource.find(sourceKey)!.enableTagsSuggestions) {
return; return;
} }
suggestionsController.findSuggestions(); suggestionsController.findSuggestions();
@@ -96,13 +96,15 @@ class _SearchResultPageState extends State<SearchResultPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ComicList( return ComicList(
key: Key(text + options.toString()), key: Key(text + options.toString() + sourceKey),
errorLeading: AppSearchBar( errorLeading: AppSearchBar(
controller: controller, controller: controller,
action: buildAction(),
), ),
leadingSliver: SliverSearchBar( leadingSliver: SliverSearchBar(
controller: controller, controller: controller,
onChanged: onChanged, onChanged: onChanged,
action: buildAction(),
), ),
loadPage: (i) { loadPage: (i) {
var source = ComicSource.find(sourceKey); var source = ComicSource.find(sourceKey);
@@ -114,6 +116,25 @@ class _SearchResultPageState extends State<SearchResultPage> {
}, },
); );
} }
Widget buildAction() {
return Tooltip(
message: "Settings".tl,
child: IconButton(
icon: const Icon(Icons.tune),
onPressed: () async {
await showDialog(
context: context,
useRootNavigator: true,
builder: (context) {
return _SearchSettingsDialog(state: this);
},
);
setState(() {});
},
),
);
}
} }
class _SuggestionsController { class _SuggestionsController {
@@ -308,7 +329,7 @@ class _SuggestionsState extends State<_Suggestions> {
controller.text = controller.text =
controller.text.replaceLast(words[words.length - 1], ""); controller.text.replaceLast(words[words.length - 1], "");
} }
if(text.contains(' ')) { if (text.contains(' ')) {
text = "'$text'"; text = "'$text'";
} }
if (type != null) { if (type != null) {
@@ -320,3 +341,117 @@ class _SuggestionsState extends State<_Suggestions> {
widget.controller.remove(); widget.controller.remove();
} }
} }
class _SearchSettingsDialog extends StatefulWidget {
const _SearchSettingsDialog({required this.state});
final _SearchResultPageState state;
@override
State<_SearchSettingsDialog> createState() => _SearchSettingsDialogState();
}
class _SearchSettingsDialogState extends State<_SearchSettingsDialog> {
late String searchTarget;
late List<String> options;
@override
void initState() {
searchTarget = widget.state.sourceKey;
options = widget.state.options;
super.initState();
}
void onChanged() {
widget.state.sourceKey = searchTarget;
widget.state.options = options;
}
@override
Widget build(BuildContext context) {
return ContentDialog(
title: "Settings".tl,
content: Column(
children: [
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
title: Text("Search in".tl),
),
Wrap(
spacing: 8,
runSpacing: 8,
children: ComicSource.all().map((e) {
return OptionChip(
text: e.name.tl,
isSelected: searchTarget == e.key,
onTap: () {
setState(() {
searchTarget = e.key;
options.clear();
onChanged();
});
},
);
}).toList(),
),
buildSearchOptions(),
const SizedBox(height: 24),
FilledButton(
child: Text("Confirm".tl),
onPressed: () {
context.pop();
},
),
],
).fixWidth(400),
);
}
Widget buildSearchOptions() {
var children = <Widget>[];
final searchOptions =
ComicSource.find(searchTarget)!.searchPageData!.searchOptions ??
<SearchOptions>[];
if (searchOptions.length != options.length) {
options = searchOptions.map((e) => e.defaultValue).toList();
}
if (searchOptions.isEmpty) {
return const SizedBox();
}
for (int i = 0; i < searchOptions.length; i++) {
final option = searchOptions[i];
children.add(ListTile(
contentPadding: EdgeInsets.zero,
title: Text(option.label.tl),
));
children.add(Wrap(
runSpacing: 8,
spacing: 8,
children: option.options.entries.map((e) {
return OptionChip(
text: e.value.ts(searchTarget),
isSelected: options[i] == e.key,
onTap: () {
setState(() {
options[i] = e.key;
});
onChanged();
},
);
}).toList(),
));
}
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: children,
),
);
}
}