From a465195ec46c20b22e982dfb9ad486f14deba550 Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Sat, 17 Aug 2024 06:24:21 +0200 Subject: [PATCH] refactored selectable chips --- .../presentation/widgets/bang_chips.dart | 69 ----------------- .../presentation/widgets/site_search.dart | 20 +++-- .../presentation/widgets/tabs/search_tab.dart | 24 +++--- .../widgets/selectable_chips.dart | 74 +++++++++++++++++++ 4 files changed, 100 insertions(+), 87 deletions(-) delete mode 100644 app/lib/features/bangs/presentation/widgets/bang_chips.dart create mode 100644 app/lib/presentation/widgets/selectable_chips.dart diff --git a/app/lib/features/bangs/presentation/widgets/bang_chips.dart b/app/lib/features/bangs/presentation/widgets/bang_chips.dart deleted file mode 100644 index dc5039cc..00000000 --- a/app/lib/features/bangs/presentation/widgets/bang_chips.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lensai/features/bangs/data/models/bang_data.dart'; -import 'package:lensai/features/bangs/presentation/widgets/bang_icon.dart'; - -class BangChips extends StatelessWidget { - final List availableBangs; - final BangData? selectedBang; - final int maxBangCount; - - final void Function(String trigger)? onSelected; - final void Function(String trigger)? onDeleted; - - const BangChips({ - required this.availableBangs, - this.selectedBang, - this.maxBangCount = 25, - this.onSelected, - this.onDeleted, - super.key, - }); - - @override - Widget build(BuildContext context) { - var bangs = availableBangs.take(maxBangCount).toList(); - if (selectedBang != null) { - final selectedIndex = - bangs.indexWhere((bang) => bang.trigger == selectedBang?.trigger); - if (selectedIndex < 0) { - bangs = [ - selectedBang!, - ...bangs, - ]; - } else { - bangs = [ - bangs.removeAt(selectedIndex), - ...bangs, - ]; - } - } - - return ListView.builder( - scrollDirection: Axis.horizontal, - shrinkWrap: true, - itemCount: bangs.length, - itemBuilder: (context, index) { - final bang = bangs[index]; - return Padding( - padding: const EdgeInsets.only(right: 8.0), - child: FilterChip( - selected: bang.trigger == selectedBang?.trigger, - showCheckmark: false, - onSelected: (value) { - if (value) { - onSelected?.call(bang.trigger); - } else { - onDeleted?.call(bang.trigger); - } - }, - onDeleted: () { - onDeleted?.call(bang.trigger); - }, - avatar: BangIcon(bang, iconSize: 20), - label: Text(bang.websiteName), - ), - ); - }, - ); - } -} diff --git a/app/lib/features/bangs/presentation/widgets/site_search.dart b/app/lib/features/bangs/presentation/widgets/site_search.dart index e9ed6854..f68eeaf9 100644 --- a/app/lib/features/bangs/presentation/widgets/site_search.dart +++ b/app/lib/features/bangs/presentation/widgets/site_search.dart @@ -4,10 +4,11 @@ import 'package:flutter_material_design_icons/flutter_material_design_icons.dart import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:lensai/features/bangs/data/models/bang_data.dart'; import 'package:lensai/features/bangs/domain/repositories/data.dart'; -import 'package:lensai/features/bangs/presentation/widgets/bang_chips.dart'; +import 'package:lensai/features/bangs/presentation/widgets/bang_icon.dart'; import 'package:lensai/features/bangs/presentation/widgets/search_field.dart'; import 'package:lensai/features/search_browser/domain/providers.dart'; import 'package:lensai/features/web_view/presentation/controllers/switch_new_tab.dart'; +import 'package:lensai/presentation/widgets/selectable_chips.dart'; class SiteSearch extends HookConsumerWidget { final String domain; @@ -54,21 +55,24 @@ class SiteSearch extends HookConsumerWidget { SizedBox( height: 48, width: double.maxFinite, - child: BangChips( - availableBangs: availableBangs, - selectedBang: selectedBang, - onSelected: (trigger) { + child: SelectableChips( + itemId: (bang) => bang.trigger, + itemAvatar: (bang) => BangIcon(bang, iconSize: 20), + itemLabel: (bang) => Text(bang.websiteName), + available: availableBangs, + selected: selectedBang, + onSelected: (bang) { ref .read( selectedBangTriggerProvider(domain: domain).notifier, ) - .setTrigger(trigger); + .setTrigger(bang.trigger); }, - onDeleted: (trigger) { + onDeleted: (bang) { if (ref.read( selectedBangTriggerProvider(domain: domain), ) == - trigger) { + bang.trigger) { ref .read( selectedBangTriggerProvider(domain: domain).notifier, diff --git a/app/lib/features/search_browser/presentation/widgets/tabs/search_tab.dart b/app/lib/features/search_browser/presentation/widgets/tabs/search_tab.dart index 1547526a..3b84e9ee 100644 --- a/app/lib/features/search_browser/presentation/widgets/tabs/search_tab.dart +++ b/app/lib/features/search_browser/presentation/widgets/tabs/search_tab.dart @@ -6,11 +6,12 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:lensai/core/routing/routes.dart'; import 'package:lensai/features/bangs/domain/providers.dart'; import 'package:lensai/features/bangs/domain/repositories/data.dart'; -import 'package:lensai/features/bangs/presentation/widgets/bang_chips.dart'; +import 'package:lensai/features/bangs/presentation/widgets/bang_icon.dart'; import 'package:lensai/features/bangs/presentation/widgets/search_field.dart'; import 'package:lensai/features/search_browser/domain/providers.dart'; import 'package:lensai/features/search_browser/presentation/widgets/sheets/shared_content_sheet.dart'; import 'package:lensai/features/share_intent/domain/entities/shared_content.dart'; +import 'package:lensai/presentation/widgets/selectable_chips.dart'; class SearchTab extends HookConsumerWidget { final SharedContent? sharedContent; @@ -66,17 +67,20 @@ class SearchTab extends HookConsumerWidget { children: [ if (selectedBang != null || availableBangs.isNotEmpty) Expanded( - child: BangChips( - availableBangs: availableBangs, - selectedBang: selectedBang, - onSelected: (trigger) { + child: SelectableChips( + itemId: (bang) => bang.trigger, + itemAvatar: (bang) => BangIcon(bang, iconSize: 20), + itemLabel: (bang) => Text(bang.websiteName), + available: availableBangs, + selected: selectedBang, + onSelected: (bang) { ref .read(selectedBangTriggerProvider().notifier) - .setTrigger(trigger); + .setTrigger(bang.trigger); }, - onDeleted: (trigger) async { + onDeleted: (bang) async { if (ref.read(selectedBangTriggerProvider()) == - trigger) { + bang.trigger) { ref .read( selectedBangTriggerProvider().notifier, @@ -87,7 +91,7 @@ class SearchTab extends HookConsumerWidget { context: context, builder: (context) => AlertDialog( title: Text( - 'Reset usage frequency of !$trigger?', + 'Reset usage frequency of !${bang.trigger}?', ), content: const Text( 'This will remove the Bang from quick select.', @@ -110,7 +114,7 @@ class SearchTab extends HookConsumerWidget { if (dialogResult == true) { await ref .read(bangDataRepositoryProvider.notifier) - .resetFrequency(trigger); + .resetFrequency(bang.trigger); } } }, diff --git a/app/lib/presentation/widgets/selectable_chips.dart b/app/lib/presentation/widgets/selectable_chips.dart new file mode 100644 index 00000000..87653058 --- /dev/null +++ b/app/lib/presentation/widgets/selectable_chips.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; + +class SelectableChips extends StatelessWidget { + final List available; + final T? selected; + final int maxCount; + + final K Function(T item) itemId; + final Widget Function(T item) itemLabel; + final Widget Function(T item)? itemAvatar; + + final void Function(T item)? onSelected; + final void Function(T item)? onDeleted; + + const SelectableChips({ + required this.itemId, + required this.itemLabel, + this.itemAvatar, + required this.available, + this.selected, + this.maxCount = 25, + this.onSelected, + this.onDeleted, + super.key, + }); + + @override + Widget build(BuildContext context) { + var items = available.take(maxCount).toList(); + if (selected != null) { + final selectedIndex = items.indexWhere( + (item) => selected != null && itemId(item) == itemId(selected!)); + if (selectedIndex < 0) { + items = [ + selected!, + ...items, + ]; + } else { + items = [ + items.removeAt(selectedIndex), + ...items, + ]; + } + } + + return ListView.builder( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + itemCount: items.length, + itemBuilder: (context, index) { + final item = items[index]; + return Padding( + padding: const EdgeInsets.only(right: 8.0), + child: FilterChip( + selected: selected != null && itemId(item) == itemId(selected!), + showCheckmark: false, + onSelected: (value) { + if (value) { + onSelected?.call(item); + } else { + onDeleted?.call(item); + } + }, + onDeleted: () { + onDeleted?.call(item); + }, + label: itemLabel.call(item), + avatar: itemAvatar?.call(item), + ), + ); + }, + ); + } +}