diff --git a/lib/components/adaptive/adaptive_select_tile.dart b/lib/components/adaptive/adaptive_select_tile.dart index 94af6fd9..80e4ae80 100644 --- a/lib/components/adaptive/adaptive_select_tile.dart +++ b/lib/components/adaptive/adaptive_select_tile.dart @@ -54,7 +54,16 @@ class AdaptiveSelectTile extends HookWidget { onChanged: onChanged, popupConstraints: popupConstraints ?? const BoxConstraints(maxWidth: 200), popupWidthConstraint: popupWidthConstraint ?? PopoverConstraint.flexible, - children: options, + popup: (context) { + return SelectPopup( + items: SelectItemBuilder( + childCount: options.length, + builder: (context, index) { + return options[index]; + }, + ), + ); + }, ); if (mediaQuery.smAndDown) { diff --git a/lib/components/form/text_form_field.dart b/lib/components/form/text_form_field.dart index ef3514c5..56ef34a5 100644 --- a/lib/components/form/text_form_field.dart +++ b/lib/components/form/text_form_field.dart @@ -11,9 +11,9 @@ class TextFormBuilderField extends StatelessWidget { final TextEditingController? controller; final bool filled; final Widget? placeholder; - final AlignmentGeometry? placeholderAlignment; - final AlignmentGeometry? leadingAlignment; - final AlignmentGeometry? trailingAlignment; + // final AlignmentGeometry? placeholderAlignment; + // final AlignmentGeometry? leadingAlignment; + // final AlignmentGeometry? trailingAlignment; final bool border; final Widget? leading; final Widget? trailing; @@ -41,9 +41,9 @@ class TextFormBuilderField extends StatelessWidget { final void Function(PointerDownEvent event)? onTapOutside; final List? inputFormatters; final TextStyle? style; - final EditableTextContextMenuBuilder? contextMenuBuilder; - final bool useNativeContextMenu; - final bool? isCollapsed; + // final EditableTextContextMenuBuilder? contextMenuBuilder; + // final bool useNativeContextMenu; + // final bool? isCollapsed; final TextInputType? keyboardType; final TextInputAction? textInputAction; final Clip clipBehavior; @@ -86,15 +86,15 @@ class TextFormBuilderField extends StatelessWidget { this.onTapOutside, this.inputFormatters, this.style, - this.contextMenuBuilder = TextField.defaultContextMenuBuilder, - this.useNativeContextMenu = false, - this.isCollapsed, + // this.contextMenuBuilder = TextField.defaultContextMenuBuilder, + // this.useNativeContextMenu = false, + // this.isCollapsed, this.textInputAction, this.clipBehavior = Clip.hardEdge, this.autofocus = false, - this.placeholderAlignment, - this.leadingAlignment, - this.trailingAlignment, + // this.placeholderAlignment, + // this.leadingAlignment, + // this.trailingAlignment, this.statesController, }); @@ -161,16 +161,16 @@ class TextFormBuilderField extends StatelessWidget { onTapOutside: onTapOutside, inputFormatters: inputFormatters, style: style, - contextMenuBuilder: contextMenuBuilder, - useNativeContextMenu: useNativeContextMenu, - isCollapsed: isCollapsed, + // contextMenuBuilder: contextMenuBuilder, + // useNativeContextMenu: useNativeContextMenu, + // isCollapsed: isCollapsed, keyboardType: keyboardType, textInputAction: textInputAction, clipBehavior: clipBehavior, autofocus: autofocus, - placeholderAlignment: placeholderAlignment, - leadingAlignment: leadingAlignment, - trailingAlignment: trailingAlignment, + // placeholderAlignment: placeholderAlignment, + // leadingAlignment: leadingAlignment, + // trailingAlignment: trailingAlignment, statesController: statesController, ), if (field.hasError) diff --git a/lib/components/track_presentation/presentation_modifiers.dart b/lib/components/track_presentation/presentation_modifiers.dart index 4d781d24..c46fef3f 100644 --- a/lib/components/track_presentation/presentation_modifiers.dart +++ b/lib/components/track_presentation/presentation_modifiers.dart @@ -1,4 +1,3 @@ -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; @@ -9,6 +8,7 @@ import 'package:spotube/components/track_presentation/presentation_props.dart'; import 'package:spotube/components/track_presentation/presentation_state.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/hooks/controllers/use_shadcn_text_editing_controller.dart'; class TrackPresentationModifiersSection extends HookConsumerWidget { final FocusNode? focusNode; @@ -25,7 +25,7 @@ class TrackPresentationModifiersSection extends HookConsumerWidget { presentationStateProvider(options.collection).notifier, ); - final controller = useTextEditingController(); + final controller = useShadcnTextEditingController(); final scale = context.theme.scaling; return LayoutBuilder(builder: (context, constrains) { diff --git a/lib/hooks/controllers/use_shadcn_text_editing_controller.dart b/lib/hooks/controllers/use_shadcn_text_editing_controller.dart new file mode 100644 index 00000000..ae33f4e4 --- /dev/null +++ b/lib/hooks/controllers/use_shadcn_text_editing_controller.dart @@ -0,0 +1,97 @@ +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; + +class _TextEditingControllerHookCreator { + const _TextEditingControllerHookCreator(); + + /// Creates a [TextEditingController] that will be disposed automatically. + /// + /// The [text] parameter can be used to set the initial value of the + /// controller. + TextEditingController call({String? text, List? keys}) { + return use(_TextEditingControllerHook(text, keys)); + } + + /// Creates a [TextEditingController] from the initial [value] that will + /// be disposed automatically. + TextEditingController fromValue( + TextEditingValue value, [ + List? keys, + ]) { + return use(_TextEditingControllerHook.fromValue(value, keys)); + } +} + +/// Creates a [TextEditingController], either via an initial text or an initial +/// [TextEditingValue]. +/// +/// To use a [TextEditingController] with an optional initial text, use: +/// ```dart +/// final controller = useTextEditingController(text: 'initial text'); +/// ``` +/// +/// To use a [TextEditingController] with an optional initial value, use: +/// ```dart +/// final controller = useTextEditingController +/// .fromValue(TextEditingValue.empty); +/// ``` +/// +/// Changing the text or initial value after the widget has been built has no +/// effect whatsoever. To update the value in a callback, for instance after a +/// button was pressed, use the [TextEditingController.text] or +/// [TextEditingController.value] setters. To have the [TextEditingController] +/// reflect changing values, you can use [useEffect]. This example will update +/// the [TextEditingController.text] whenever a provided [ValueListenable] +/// changes: +/// ```dart +/// final controller = useTextEditingController(); +/// final update = useValueListenable(myTextControllerUpdates); +/// +/// useEffect(() { +/// controller.text = update; +/// }, [update]); +/// ``` +/// +/// See also: +/// - [TextEditingController], which this hook creates. +const useShadcnTextEditingController = _TextEditingControllerHookCreator(); + +class _TextEditingControllerHook extends Hook { + const _TextEditingControllerHook( + this.initialText, [ + List? keys, + ]) : initialValue = null, + super(keys: keys); + + const _TextEditingControllerHook.fromValue( + TextEditingValue this.initialValue, [ + List? keys, + ]) : initialText = null, + super(keys: keys); + + final String? initialText; + final TextEditingValue? initialValue; + + @override + _TextEditingControllerHookState createState() { + return _TextEditingControllerHookState(); + } +} + +class _TextEditingControllerHookState + extends HookState { + late final _controller = hook.initialValue != null + ? TextEditingController.fromValue( + hook.initialValue ?? TextEditingValue.empty, + ) + : TextEditingController(text: hook.initialText); + + @override + TextEditingController build(BuildContext context) => _controller; + + @override + void dispose() => _controller.dispose(); + + @override + String get debugLabel => 'useTextEditingController'; +} diff --git a/lib/modules/library/playlist_generate/recommendation_attribute_fields.dart b/lib/modules/library/playlist_generate/recommendation_attribute_fields.dart index 351fde1e..b616b080 100644 --- a/lib/modules/library/playlist_generate/recommendation_attribute_fields.dart +++ b/lib/modules/library/playlist_generate/recommendation_attribute_fields.dart @@ -1,5 +1,6 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:spotube/hooks/controllers/use_shadcn_text_editing_controller.dart'; import 'package:spotube/modules/library/playlist_generate/recommendation_attribute_dials.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; @@ -21,10 +22,12 @@ class RecommendationAttributeFields extends HookWidget { @override Widget build(BuildContext context) { - final minController = useTextEditingController(text: values.min.toString()); + final minController = + useShadcnTextEditingController(text: values.min.toString()); final targetController = - useTextEditingController(text: values.target.toString()); - final maxController = useTextEditingController(text: values.max.toString()); + useShadcnTextEditingController(text: values.target.toString()); + final maxController = + useShadcnTextEditingController(text: values.max.toString()); useEffect(() { listener() { diff --git a/lib/modules/library/playlist_generate/seeds_multi_autocomplete.dart b/lib/modules/library/playlist_generate/seeds_multi_autocomplete.dart index 8c19ca6c..da1288f5 100644 --- a/lib/modules/library/playlist_generate/seeds_multi_autocomplete.dart +++ b/lib/modules/library/playlist_generate/seeds_multi_autocomplete.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart' show Autocomplete; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/extensions/constrains.dart'; +import 'package:spotube/hooks/controllers/use_shadcn_text_editing_controller.dart'; enum SelectedItemDisplayType { wrap, @@ -49,7 +50,7 @@ class SeedsMultiAutocomplete extends HookWidget { useValueListenable(seeds); final theme = Theme.of(context); final mediaQuery = MediaQuery.of(context); - final seedController = useTextEditingController(); + final seedController = useShadcnTextEditingController(); final containerKey = useRef(GlobalKey()); diff --git a/lib/modules/player/sibling_tracks_sheet.dart b/lib/modules/player/sibling_tracks_sheet.dart index ccc1bfcd..8fa413aa 100644 --- a/lib/modules/player/sibling_tracks_sheet.dart +++ b/lib/modules/player/sibling_tracks_sheet.dart @@ -13,6 +13,7 @@ import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/duration.dart'; +import 'package:spotube/hooks/controllers/use_shadcn_text_editing_controller.dart'; import 'package:spotube/hooks/utils/use_debounce.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; @@ -85,7 +86,7 @@ class SiblingTracksSheet extends HookConsumerWidget { final defaultSearchTerm = "$title - ${activeTrack?.artists?.asString() ?? ""}"; - final searchController = useTextEditingController( + final searchController = useShadcnTextEditingController( text: defaultSearchTerm, ); diff --git a/lib/modules/settings/youtube_engine_not_installed_dialog.dart b/lib/modules/settings/youtube_engine_not_installed_dialog.dart index bc18bc66..b993dd1b 100644 --- a/lib/modules/settings/youtube_engine_not_installed_dialog.dart +++ b/lib/modules/settings/youtube_engine_not_installed_dialog.dart @@ -8,6 +8,7 @@ import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/form/text_form_field.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/hooks/controllers/use_shadcn_text_editing_controller.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/services/kv_store/kv_store.dart'; import 'package:spotube/utils/platform.dart'; @@ -28,7 +29,7 @@ class YouTubeEngineNotInstalledDialog extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final controller = useTextEditingController(); + final controller = useShadcnTextEditingController(); final formKey = useMemoized(() => GlobalKey(), []); return AlertDialog( diff --git a/lib/modules/stats/summary/summary.dart b/lib/modules/stats/summary/summary.dart index 352d9ed3..30e68b1f 100644 --- a/lib/modules/stats/summary/summary.dart +++ b/lib/modules/stats/summary/summary.dart @@ -63,7 +63,7 @@ class StatsPageSummarySection extends HookConsumerWidget { description: context.l10n.summary_owed_to_artists, color: Colors.green, onTap: () { - context.navigateTo(const StatsStreamsRoute()); + context.navigateTo(const StatsStreamFeesRoute()); }, ), SummaryCard( diff --git a/lib/modules/stats/top/top.dart b/lib/modules/stats/top/top.dart index 2834ce1e..38f04ccb 100644 --- a/lib/modules/stats/top/top.dart +++ b/lib/modules/stats/top/top.dart @@ -30,24 +30,30 @@ class StatsPageTopSection extends HookConsumerWidget { }; final dropdown = Select( - popupConstraints: const BoxConstraints(maxWidth: 150), - popupWidthConstraint: PopoverConstraint.flexible, - padding: const EdgeInsets.all(4), - borderRadius: BorderRadius.circular(4), - value: historyDuration, - onChanged: (value) { - if (value == null) return; - historyDurationNotifier.update((_) => value); - }, - itemBuilder: (context, item) => Text(translations[item]!), - children: [ - for (final item in HistoryDuration.values) - SelectItemButton( - value: item, - child: Text(translations[item]!), - ), - ], - ); + popupConstraints: const BoxConstraints(maxWidth: 150), + popupWidthConstraint: PopoverConstraint.flexible, + padding: const EdgeInsets.all(4), + borderRadius: BorderRadius.circular(4), + value: historyDuration, + onChanged: (value) { + if (value == null) return; + historyDurationNotifier.update((_) => value); + }, + itemBuilder: (context, item) => Text(translations[item]!), + popup: (context) { + return SelectPopup( + items: SelectItemBuilder( + childCount: HistoryDuration.values.length, + builder: (context, index) { + final item = HistoryDuration.values[index]; + return SelectItemButton( + value: item, + child: Text(translations[item]!), + ); + }, + ), + ); + }); return SliverLayoutBuilder(builder: (context, constraints) { return SliverMainAxisGroup( diff --git a/lib/pages/getting_started/sections/playback.dart b/lib/pages/getting_started/sections/playback.dart index bf12d426..f122dbcd 100644 --- a/lib/pages/getting_started/sections/playback.dart +++ b/lib/pages/getting_started/sections/playback.dart @@ -84,20 +84,28 @@ class GettingStartedPagePlaybackSection extends HookConsumerWidget { Text(value.name.capitalize()), ], ), - children: [ - for (final source in AudioSource.values) - SelectItemButton( - value: source, - child: Row( - mainAxisSize: MainAxisSize.min, - spacing: 6, - children: [ - audioSourceToIconMap[source]!, - Text(source.name.capitalize()), - ], - ), + popup: (context) { + return SelectPopup( + items: SelectItemBuilder( + childCount: AudioSource.values.length, + builder: (context, index) { + final source = AudioSource.values[index]; + + return SelectItemButton( + value: source, + child: Row( + mainAxisSize: MainAxisSize.min, + spacing: 6, + children: [ + audioSourceToIconMap[source]!, + Text(source.name.capitalize()), + ], + ), + ); + }, ), - ], + ); + }, ), const Gap(16), Text( diff --git a/lib/pages/getting_started/sections/region.dart b/lib/pages/getting_started/sections/region.dart index 19507fe9..0cd09be7 100644 --- a/lib/pages/getting_started/sections/region.dart +++ b/lib/pages/getting_started/sections/region.dart @@ -14,6 +14,22 @@ class GettingStartedPageLanguageRegionSection extends HookConsumerWidget { const GettingStartedPageLanguageRegionSection( {super.key, required this.onNext}); + bool filterMarkets(Market item, String query) { + final market = spotifyMarkets + .firstWhere((element) => element.$1 == item) + .$2 + .toLowerCase(); + + return market.contains(query.toLowerCase()); + } + + bool filterLocale(Locale locale, String query) { + final language = + LanguageLocals.getDisplayLanguage(locale.languageCode).toString(); + + return language.toLowerCase().contains(query.toLowerCase()); + } + @override Widget build(BuildContext context, ref) { final preferences = ref.watch(userPreferencesProvider); @@ -62,22 +78,30 @@ class GettingStartedPageLanguageRegionSection extends HookConsumerWidget { .firstWhere((element) => element.$1 == value) .$2, ), - searchPlaceholder: Text(context.l10n.search), - searchFilter: (item, query) { - final market = spotifyMarkets - .firstWhere((element) => element.$1 == item) - .$2 - .toLowerCase(); - - return market.contains(query.toLowerCase()) ? 1 : 0; - }, - children: [ - for (final market in spotifyMarkets) - SelectItemButton( - value: market.$1, - child: Text(market.$2), - ), - ], + popup: SelectPopup.builder( + searchPlaceholder: Text(context.l10n.search), + builder: (context, searchQuery) { + final filteredMarkets = searchQuery == null || + searchQuery.isEmpty + ? spotifyMarkets + : spotifyMarkets + .where( + (element) => + filterMarkets(element.$1, searchQuery), + ) + .toList(); + return SelectItemBuilder( + childCount: filteredMarkets.length, + builder: (context, index) { + final market = filteredMarkets[index]; + return SelectItemButton( + value: market.$1, + child: Text(market.$2), + ); + }, + ); + }, + ).call, ), ), const Gap(36), @@ -106,33 +130,46 @@ class GettingStartedPageLanguageRegionSection extends HookConsumerWidget { value.languageCode) .toString(), ), - searchPlaceholder: Text(context.l10n.search), - searchFilter: (locale, query) { - final language = LanguageLocals.getDisplayLanguage( - locale.languageCode) - .toString(); + popup: SelectPopup.builder( + searchPlaceholder: Text(context.l10n.search), + builder: (context, searchQuery) { + final filteredLocale = searchQuery?.isNotEmpty != true + ? L10n.all + : L10n.all + .where( + (element) => + filterLocale(element, searchQuery!), + ) + .toList(); - return language - .toLowerCase() - .contains(query.toLowerCase()) - ? 1 - : 0; - }, - children: [ - SelectItemButton( - value: const Locale("system", "system"), - child: Text(context.l10n.system_default), - ), - for (final locale in L10n.all) - SelectItemButton( - value: locale, - child: Text( - LanguageLocals.getDisplayLanguage( - locale.languageCode) - .toString(), - ), - ), - ], + return SelectItemBuilder( + childCount: filteredLocale.length + 1, + builder: (context, index) { + if (index == 0 && + searchQuery?.isNotEmpty != true) { + return SelectItemButton( + value: const Locale("system", "system"), + child: Text(context.l10n.system_default), + ); + } + + final indexThen = searchQuery?.isNotEmpty != true + ? index + : index - 1; + + final locale = filteredLocale[indexThen]; + return SelectItemButton( + value: locale, + child: Text( + LanguageLocals.getDisplayLanguage( + locale.languageCode) + .toString(), + ), + ); + }, + ); + }, + ).call, ), ), ], diff --git a/lib/pages/library/playlist_generate/playlist_generate.dart b/lib/pages/library/playlist_generate/playlist_generate.dart index 573d502c..c0b77452 100644 --- a/lib/pages/library/playlist_generate/playlist_generate.dart +++ b/lib/pages/library/playlist_generate/playlist_generate.dart @@ -193,16 +193,11 @@ class PlaylistGeneratorPage extends HookConsumerWidget { final genreSelector = MultiSelect( value: genres.value, - searchFilter: (item, query) { - return item.toLowerCase().contains(query.toLowerCase()) ? 1 : 0; - }, onChanged: (value) { if (!enabled) return; - genres.value = value; + genres.value = value?.toList() ?? []; }, itemBuilder: (context, item) => Text(item), - searchPlaceholder: Text(context.l10n.select_genres), - orderSelectedFirst: false, popoverAlignment: Alignment.bottomCenter, popupConstraints: BoxConstraints( maxHeight: MediaQuery.sizeOf(context).height * .8, @@ -213,13 +208,33 @@ class PlaylistGeneratorPage extends HookConsumerWidget { context.l10n.genre, ), ), - children: [ - for (final option in genresCollection.asData?.value ?? []) - SelectItemButton( - value: option, - child: Text(option), - ), - ], + popup: SelectPopup.builder( + searchPlaceholder: Text(context.l10n.select_genres), + builder: (context, searchQuery) { + final filteredGenres = searchQuery?.isNotEmpty != true + ? genresCollection.asData?.value ?? [] + : genresCollection.asData?.value + .where( + (item) => item + .toLowerCase() + .contains(searchQuery!.toLowerCase()), + ) + .toList() ?? + []; + + return SelectItemBuilder( + childCount: filteredGenres.length, + builder: (context, index) { + final option = filteredGenres[index]; + + return SelectItemButton( + value: option, + child: Text(option), + ); + }, + ); + }, + ).call, ); final countrySelector = ValueListenableBuilder( @@ -231,25 +246,35 @@ class PlaylistGeneratorPage extends HookConsumerWidget { onChanged: (value) { market.value = value!; }, - searchFilter: (item, query) { - return item.name.toLowerCase().contains(query.toLowerCase()) - ? 1 - : 0; - }, - searchPlaceholder: Text(context.l10n.search), popupConstraints: BoxConstraints( maxHeight: MediaQuery.sizeOf(context).height * .8, ), popoverAlignment: Alignment.bottomCenter, itemBuilder: (context, value) => Text(value.name), - children: spotifyMarkets - .map( - (country) => SelectItemButton( - value: country.$1, - child: Text(country.$2), - ), - ) - .toList(), + popup: SelectPopup.builder( + searchPlaceholder: Text(context.l10n.search), + builder: (context, searchQuery) { + final filteredMarkets = searchQuery == null || searchQuery.isEmpty + ? spotifyMarkets + : spotifyMarkets + .where( + (item) => item.$1.name + .toLowerCase() + .contains(searchQuery.toLowerCase()), + ) + .toList(); + + return SelectItemBuilder( + childCount: filteredMarkets.length, + builder: (context, index) { + return SelectItemButton( + value: filteredMarkets[index].$1, + child: Text(filteredMarkets[index].$2), + ); + }, + ); + }, + ).call, ); }, ); diff --git a/lib/pages/library/user_local_tracks/local_folder.dart b/lib/pages/library/user_local_tracks/local_folder.dart index c3833659..a6f3ad51 100644 --- a/lib/pages/library/user_local_tracks/local_folder.dart +++ b/lib/pages/library/user_local_tracks/local_folder.dart @@ -16,6 +16,7 @@ import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/button/back_button.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/string.dart'; +import 'package:spotube/hooks/controllers/use_shadcn_text_editing_controller.dart'; import 'package:spotube/modules/library/local_folder/cache_export_dialog.dart'; import 'package:spotube/pages/library/user_local_tracks/user_local_tracks.dart'; import 'package:spotube/components/expandable_search/expandable_search.dart'; @@ -79,7 +80,7 @@ class LocalLibraryPage extends HookConsumerWidget { final isPlaylistPlaying = playlist.containsTracks( trackSnapshot.asData?.value.values.flattened.toList() ?? []); - final searchController = useTextEditingController(); + final searchController = useShadcnTextEditingController(); useValueListenable(searchController); final searchFocus = useFocusNode(); final isFiltering = useState(false); diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart index 1555c062..3826a0b6 100644 --- a/lib/pages/search/search.dart +++ b/lib/pages/search/search.dart @@ -14,6 +14,7 @@ import 'package:spotube/components/fallbacks/anonymous_fallback.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/hooks/controllers/use_shadcn_text_editing_controller.dart'; import 'package:spotube/pages/search/sections/albums.dart'; import 'package:spotube/pages/search/sections/artists.dart'; import 'package:spotube/pages/search/sections/playlists.dart'; @@ -35,7 +36,7 @@ class SearchPage extends HookConsumerWidget { final mediaQuery = MediaQuery.sizeOf(context); final scrollController = useScrollController(); - final controller = useSearchController(); + final controller = useShadcnTextEditingController(); final focusNode = useFocusNode(); final auth = ref.watch(authenticationProvider); @@ -120,41 +121,34 @@ class SearchPage extends HookConsumerWidget { } }, child: AutoComplete( - autofocus: true, - controller: controller, suggestions: suggestions, - leading: const Icon(SpotubeIcons.search), - textInputAction: TextInputAction.search, - placeholder: Text(context.l10n.search), - trailing: AnimatedCrossFade( - duration: - const Duration(milliseconds: 300), - crossFadeState: - controller.text.isNotEmpty - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - firstChild: IconButton.ghost( - size: ButtonSize.small, - icon: const Icon(SpotubeIcons.close), - onPressed: () { - controller.clear(); - }, + child: TextField( + autofocus: true, + controller: controller, + leading: + const Icon(SpotubeIcons.search), + textInputAction: TextInputAction.search, + placeholder: Text(context.l10n.search), + trailing: AnimatedCrossFade( + duration: + const Duration(milliseconds: 300), + crossFadeState: + controller.text.isNotEmpty + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + firstChild: IconButton.ghost( + size: ButtonSize.small, + icon: + const Icon(SpotubeIcons.close), + onPressed: () { + controller.clear(); + }, + ), + secondChild: const SizedBox.square( + dimension: 28), ), - secondChild: const SizedBox.square( - dimension: 28), + onSubmitted: onSubmitted, ), - onAcceptSuggestion: (index) { - controller.text = KVStoreService - .recentSearches[index]; - ref - .read(searchTermStateProvider - .notifier) - .state = - KVStoreService - .recentSearches[index]; - }, - onChanged: (value) {}, - onSubmitted: onSubmitted, ), ); }), diff --git a/lib/pages/stats/fees/fees.dart b/lib/pages/stats/fees/fees.dart index 6df911ce..2f1e4107 100644 --- a/lib/pages/stats/fees/fees.dart +++ b/lib/pages/stats/fees/fees.dart @@ -91,13 +91,18 @@ class StatsStreamFeesPage extends HookConsumerWidget { Text(translations[value]!), constraints: const BoxConstraints(maxWidth: 150), popupWidthConstraint: PopoverConstraint.anchorMaxSize, - children: [ - for (final entry in translations.entries) - SelectItemButton( - value: entry.key, - child: Text(entry.value), - ), - ], + popup: SelectPopup( + items: SelectItemBuilder( + childCount: translations.length, + builder: (context, index) { + final entry = translations.entries.elementAt(index); + return SelectItemButton( + value: entry.key, + child: Text(entry.value), + ); + }, + ), + ).call, ), ], ), diff --git a/pubspec.lock b/pubspec.lock index 508cdda3..df9b5c1c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2012,10 +2012,10 @@ packages: dependency: "direct main" description: name: shadcn_flutter - sha256: a04b6ce51ff8486fe9c0c3b373605ab30b823507a79a4458fc74b056edc883d8 + sha256: "1e5f40484a42217a69af254952168783d1305025d56dabc45ab16396dba84d5e" url: "https://pub.dev" source: hosted - version: "0.0.25" + version: "0.0.26" shared_preferences: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 848e56aa..bfde07eb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -104,7 +104,7 @@ dependencies: ref: dart-3-support url: https://github.com/KRTirtho/scrobblenaut.git scroll_to_index: ^3.0.1 - shadcn_flutter: ^0.0.25 + shadcn_flutter: ^0.0.26 shared_preferences: ^2.2.3 shelf: ^1.4.1 shelf_router: ^1.1.4