chore: upgrade to latest working shadcn_flutter

This commit is contained in:
Kingkor Roy Tirtho 2025-03-03 22:23:29 +06:00
parent 30bf0bed62
commit 7eb0e69dd7
18 changed files with 360 additions and 172 deletions

View File

@ -54,7 +54,16 @@ class AdaptiveSelectTile<T> extends HookWidget {
onChanged: onChanged, onChanged: onChanged,
popupConstraints: popupConstraints ?? const BoxConstraints(maxWidth: 200), popupConstraints: popupConstraints ?? const BoxConstraints(maxWidth: 200),
popupWidthConstraint: popupWidthConstraint ?? PopoverConstraint.flexible, popupWidthConstraint: popupWidthConstraint ?? PopoverConstraint.flexible,
children: options, popup: (context) {
return SelectPopup(
items: SelectItemBuilder(
childCount: options.length,
builder: (context, index) {
return options[index];
},
),
);
},
); );
if (mediaQuery.smAndDown) { if (mediaQuery.smAndDown) {

View File

@ -11,9 +11,9 @@ class TextFormBuilderField extends StatelessWidget {
final TextEditingController? controller; final TextEditingController? controller;
final bool filled; final bool filled;
final Widget? placeholder; final Widget? placeholder;
final AlignmentGeometry? placeholderAlignment; // final AlignmentGeometry? placeholderAlignment;
final AlignmentGeometry? leadingAlignment; // final AlignmentGeometry? leadingAlignment;
final AlignmentGeometry? trailingAlignment; // final AlignmentGeometry? trailingAlignment;
final bool border; final bool border;
final Widget? leading; final Widget? leading;
final Widget? trailing; final Widget? trailing;
@ -41,9 +41,9 @@ class TextFormBuilderField extends StatelessWidget {
final void Function(PointerDownEvent event)? onTapOutside; final void Function(PointerDownEvent event)? onTapOutside;
final List<TextInputFormatter>? inputFormatters; final List<TextInputFormatter>? inputFormatters;
final TextStyle? style; final TextStyle? style;
final EditableTextContextMenuBuilder? contextMenuBuilder; // final EditableTextContextMenuBuilder? contextMenuBuilder;
final bool useNativeContextMenu; // final bool useNativeContextMenu;
final bool? isCollapsed; // final bool? isCollapsed;
final TextInputType? keyboardType; final TextInputType? keyboardType;
final TextInputAction? textInputAction; final TextInputAction? textInputAction;
final Clip clipBehavior; final Clip clipBehavior;
@ -86,15 +86,15 @@ class TextFormBuilderField extends StatelessWidget {
this.onTapOutside, this.onTapOutside,
this.inputFormatters, this.inputFormatters,
this.style, this.style,
this.contextMenuBuilder = TextField.defaultContextMenuBuilder, // this.contextMenuBuilder = TextField.defaultContextMenuBuilder,
this.useNativeContextMenu = false, // this.useNativeContextMenu = false,
this.isCollapsed, // this.isCollapsed,
this.textInputAction, this.textInputAction,
this.clipBehavior = Clip.hardEdge, this.clipBehavior = Clip.hardEdge,
this.autofocus = false, this.autofocus = false,
this.placeholderAlignment, // this.placeholderAlignment,
this.leadingAlignment, // this.leadingAlignment,
this.trailingAlignment, // this.trailingAlignment,
this.statesController, this.statesController,
}); });
@ -161,16 +161,16 @@ class TextFormBuilderField extends StatelessWidget {
onTapOutside: onTapOutside, onTapOutside: onTapOutside,
inputFormatters: inputFormatters, inputFormatters: inputFormatters,
style: style, style: style,
contextMenuBuilder: contextMenuBuilder, // contextMenuBuilder: contextMenuBuilder,
useNativeContextMenu: useNativeContextMenu, // useNativeContextMenu: useNativeContextMenu,
isCollapsed: isCollapsed, // isCollapsed: isCollapsed,
keyboardType: keyboardType, keyboardType: keyboardType,
textInputAction: textInputAction, textInputAction: textInputAction,
clipBehavior: clipBehavior, clipBehavior: clipBehavior,
autofocus: autofocus, autofocus: autofocus,
placeholderAlignment: placeholderAlignment, // placeholderAlignment: placeholderAlignment,
leadingAlignment: leadingAlignment, // leadingAlignment: leadingAlignment,
trailingAlignment: trailingAlignment, // trailingAlignment: trailingAlignment,
statesController: statesController, statesController: statesController,
), ),
if (field.hasError) if (field.hasError)

View File

@ -1,4 +1,3 @@
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:shadcn_flutter/shadcn_flutter_extension.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/components/track_presentation/presentation_state.dart';
import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
import 'package:spotube/hooks/controllers/use_shadcn_text_editing_controller.dart';
class TrackPresentationModifiersSection extends HookConsumerWidget { class TrackPresentationModifiersSection extends HookConsumerWidget {
final FocusNode? focusNode; final FocusNode? focusNode;
@ -25,7 +25,7 @@ class TrackPresentationModifiersSection extends HookConsumerWidget {
presentationStateProvider(options.collection).notifier, presentationStateProvider(options.collection).notifier,
); );
final controller = useTextEditingController(); final controller = useShadcnTextEditingController();
final scale = context.theme.scaling; final scale = context.theme.scaling;
return LayoutBuilder(builder: (context, constrains) { return LayoutBuilder(builder: (context, constrains) {

View File

@ -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<Object?>? keys}) {
return use(_TextEditingControllerHook(text, keys));
}
/// Creates a [TextEditingController] from the initial [value] that will
/// be disposed automatically.
TextEditingController fromValue(
TextEditingValue value, [
List<Object?>? 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<TextEditingController> {
const _TextEditingControllerHook(
this.initialText, [
List<Object?>? keys,
]) : initialValue = null,
super(keys: keys);
const _TextEditingControllerHook.fromValue(
TextEditingValue this.initialValue, [
List<Object?>? keys,
]) : initialText = null,
super(keys: keys);
final String? initialText;
final TextEditingValue? initialValue;
@override
_TextEditingControllerHookState createState() {
return _TextEditingControllerHookState();
}
}
class _TextEditingControllerHookState
extends HookState<TextEditingController, _TextEditingControllerHook> {
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';
}

View File

@ -1,5 +1,6 @@
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:shadcn_flutter/shadcn_flutter.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/modules/library/playlist_generate/recommendation_attribute_dials.dart';
import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
@ -21,10 +22,12 @@ class RecommendationAttributeFields extends HookWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final minController = useTextEditingController(text: values.min.toString()); final minController =
useShadcnTextEditingController(text: values.min.toString());
final targetController = final targetController =
useTextEditingController(text: values.target.toString()); useShadcnTextEditingController(text: values.target.toString());
final maxController = useTextEditingController(text: values.max.toString()); final maxController =
useShadcnTextEditingController(text: values.max.toString());
useEffect(() { useEffect(() {
listener() { listener() {

View File

@ -5,6 +5,7 @@ import 'package:flutter/material.dart' show Autocomplete;
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/hooks/controllers/use_shadcn_text_editing_controller.dart';
enum SelectedItemDisplayType { enum SelectedItemDisplayType {
wrap, wrap,
@ -49,7 +50,7 @@ class SeedsMultiAutocomplete<T extends Object> extends HookWidget {
useValueListenable(seeds); useValueListenable(seeds);
final theme = Theme.of(context); final theme = Theme.of(context);
final mediaQuery = MediaQuery.of(context); final mediaQuery = MediaQuery.of(context);
final seedController = useTextEditingController(); final seedController = useShadcnTextEditingController();
final containerKey = useRef(GlobalKey()); final containerKey = useRef(GlobalKey());

View File

@ -13,6 +13,7 @@ import 'package:spotube/extensions/artist_simple.dart';
import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
import 'package:spotube/extensions/duration.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/hooks/utils/use_debounce.dart';
import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/database/database.dart';
import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/audio_player/audio_player.dart';
@ -85,7 +86,7 @@ class SiblingTracksSheet extends HookConsumerWidget {
final defaultSearchTerm = final defaultSearchTerm =
"$title - ${activeTrack?.artists?.asString() ?? ""}"; "$title - ${activeTrack?.artists?.asString() ?? ""}";
final searchController = useTextEditingController( final searchController = useShadcnTextEditingController(
text: defaultSearchTerm, text: defaultSearchTerm,
); );

View File

@ -8,6 +8,7 @@ import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/form/text_form_field.dart'; import 'package:spotube/components/form/text_form_field.dart';
import 'package:spotube/extensions/context.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/models/database/database.dart';
import 'package:spotube/services/kv_store/kv_store.dart'; import 'package:spotube/services/kv_store/kv_store.dart';
import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/platform.dart';
@ -28,7 +29,7 @@ class YouTubeEngineNotInstalledDialog extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final controller = useTextEditingController(); final controller = useShadcnTextEditingController();
final formKey = useMemoized(() => GlobalKey<FormBuilderState>(), []); final formKey = useMemoized(() => GlobalKey<FormBuilderState>(), []);
return AlertDialog( return AlertDialog(

View File

@ -63,7 +63,7 @@ class StatsPageSummarySection extends HookConsumerWidget {
description: context.l10n.summary_owed_to_artists, description: context.l10n.summary_owed_to_artists,
color: Colors.green, color: Colors.green,
onTap: () { onTap: () {
context.navigateTo(const StatsStreamsRoute()); context.navigateTo(const StatsStreamFeesRoute());
}, },
), ),
SummaryCard( SummaryCard(

View File

@ -40,14 +40,20 @@ class StatsPageTopSection extends HookConsumerWidget {
historyDurationNotifier.update((_) => value); historyDurationNotifier.update((_) => value);
}, },
itemBuilder: (context, item) => Text(translations[item]!), itemBuilder: (context, item) => Text(translations[item]!),
children: [ popup: (context) {
for (final item in HistoryDuration.values) return SelectPopup(
SelectItemButton( items: SelectItemBuilder(
childCount: HistoryDuration.values.length,
builder: (context, index) {
final item = HistoryDuration.values[index];
return SelectItemButton(
value: item, value: item,
child: Text(translations[item]!), child: Text(translations[item]!),
),
],
); );
},
),
);
});
return SliverLayoutBuilder(builder: (context, constraints) { return SliverLayoutBuilder(builder: (context, constraints) {
return SliverMainAxisGroup( return SliverMainAxisGroup(

View File

@ -84,9 +84,14 @@ class GettingStartedPagePlaybackSection extends HookConsumerWidget {
Text(value.name.capitalize()), Text(value.name.capitalize()),
], ],
), ),
children: [ popup: (context) {
for (final source in AudioSource.values) return SelectPopup(
SelectItemButton( items: SelectItemBuilder(
childCount: AudioSource.values.length,
builder: (context, index) {
final source = AudioSource.values[index];
return SelectItemButton(
value: source, value: source,
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -96,8 +101,11 @@ class GettingStartedPagePlaybackSection extends HookConsumerWidget {
Text(source.name.capitalize()), Text(source.name.capitalize()),
], ],
), ),
);
},
), ),
], );
},
), ),
const Gap(16), const Gap(16),
Text( Text(

View File

@ -14,6 +14,22 @@ class GettingStartedPageLanguageRegionSection extends HookConsumerWidget {
const GettingStartedPageLanguageRegionSection( const GettingStartedPageLanguageRegionSection(
{super.key, required this.onNext}); {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 @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final preferences = ref.watch(userPreferencesProvider); final preferences = ref.watch(userPreferencesProvider);
@ -62,22 +78,30 @@ class GettingStartedPageLanguageRegionSection extends HookConsumerWidget {
.firstWhere((element) => element.$1 == value) .firstWhere((element) => element.$1 == value)
.$2, .$2,
), ),
popup: SelectPopup.builder(
searchPlaceholder: Text(context.l10n.search), searchPlaceholder: Text(context.l10n.search),
searchFilter: (item, query) { builder: (context, searchQuery) {
final market = spotifyMarkets final filteredMarkets = searchQuery == null ||
.firstWhere((element) => element.$1 == item) searchQuery.isEmpty
.$2 ? spotifyMarkets
.toLowerCase(); : spotifyMarkets
.where(
return market.contains(query.toLowerCase()) ? 1 : 0; (element) =>
}, filterMarkets(element.$1, searchQuery),
children: [ )
for (final market in spotifyMarkets) .toList();
SelectItemButton( return SelectItemBuilder(
childCount: filteredMarkets.length,
builder: (context, index) {
final market = filteredMarkets[index];
return SelectItemButton(
value: market.$1, value: market.$1,
child: Text(market.$2), child: Text(market.$2),
), );
], },
);
},
).call,
), ),
), ),
const Gap(36), const Gap(36),
@ -106,33 +130,46 @@ class GettingStartedPageLanguageRegionSection extends HookConsumerWidget {
value.languageCode) value.languageCode)
.toString(), .toString(),
), ),
popup: SelectPopup.builder(
searchPlaceholder: Text(context.l10n.search), searchPlaceholder: Text(context.l10n.search),
searchFilter: (locale, query) { builder: (context, searchQuery) {
final language = LanguageLocals.getDisplayLanguage( final filteredLocale = searchQuery?.isNotEmpty != true
locale.languageCode) ? L10n.all
.toString(); : L10n.all
.where(
(element) =>
filterLocale(element, searchQuery!),
)
.toList();
return language return SelectItemBuilder(
.toLowerCase() childCount: filteredLocale.length + 1,
.contains(query.toLowerCase()) builder: (context, index) {
? 1 if (index == 0 &&
: 0; searchQuery?.isNotEmpty != true) {
}, return SelectItemButton(
children: [
SelectItemButton(
value: const Locale("system", "system"), value: const Locale("system", "system"),
child: Text(context.l10n.system_default), child: Text(context.l10n.system_default),
), );
for (final locale in L10n.all) }
SelectItemButton(
final indexThen = searchQuery?.isNotEmpty != true
? index
: index - 1;
final locale = filteredLocale[indexThen];
return SelectItemButton(
value: locale, value: locale,
child: Text( child: Text(
LanguageLocals.getDisplayLanguage( LanguageLocals.getDisplayLanguage(
locale.languageCode) locale.languageCode)
.toString(), .toString(),
), ),
), );
], },
);
},
).call,
), ),
), ),
], ],

View File

@ -193,16 +193,11 @@ class PlaylistGeneratorPage extends HookConsumerWidget {
final genreSelector = MultiSelect<String>( final genreSelector = MultiSelect<String>(
value: genres.value, value: genres.value,
searchFilter: (item, query) {
return item.toLowerCase().contains(query.toLowerCase()) ? 1 : 0;
},
onChanged: (value) { onChanged: (value) {
if (!enabled) return; if (!enabled) return;
genres.value = value; genres.value = value?.toList() ?? [];
}, },
itemBuilder: (context, item) => Text(item), itemBuilder: (context, item) => Text(item),
searchPlaceholder: Text(context.l10n.select_genres),
orderSelectedFirst: false,
popoverAlignment: Alignment.bottomCenter, popoverAlignment: Alignment.bottomCenter,
popupConstraints: BoxConstraints( popupConstraints: BoxConstraints(
maxHeight: MediaQuery.sizeOf(context).height * .8, maxHeight: MediaQuery.sizeOf(context).height * .8,
@ -213,13 +208,33 @@ class PlaylistGeneratorPage extends HookConsumerWidget {
context.l10n.genre, context.l10n.genre,
), ),
), ),
children: [ popup: SelectPopup.builder(
for (final option in genresCollection.asData?.value ?? <String>[]) searchPlaceholder: Text(context.l10n.select_genres),
SelectItemButton( 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, value: option,
child: Text(option), child: Text(option),
), );
], },
);
},
).call,
); );
final countrySelector = ValueListenableBuilder( final countrySelector = ValueListenableBuilder(
@ -231,25 +246,35 @@ class PlaylistGeneratorPage extends HookConsumerWidget {
onChanged: (value) { onChanged: (value) {
market.value = value!; market.value = value!;
}, },
searchFilter: (item, query) {
return item.name.toLowerCase().contains(query.toLowerCase())
? 1
: 0;
},
searchPlaceholder: Text(context.l10n.search),
popupConstraints: BoxConstraints( popupConstraints: BoxConstraints(
maxHeight: MediaQuery.sizeOf(context).height * .8, maxHeight: MediaQuery.sizeOf(context).height * .8,
), ),
popoverAlignment: Alignment.bottomCenter, popoverAlignment: Alignment.bottomCenter,
itemBuilder: (context, value) => Text(value.name), itemBuilder: (context, value) => Text(value.name),
children: spotifyMarkets popup: SelectPopup.builder(
.map( searchPlaceholder: Text(context.l10n.search),
(country) => SelectItemButton( builder: (context, searchQuery) {
value: country.$1, final filteredMarkets = searchQuery == null || searchQuery.isEmpty
child: Text(country.$2), ? spotifyMarkets
), : spotifyMarkets
.where(
(item) => item.$1.name
.toLowerCase()
.contains(searchQuery.toLowerCase()),
) )
.toList(), .toList();
return SelectItemBuilder(
childCount: filteredMarkets.length,
builder: (context, index) {
return SelectItemButton(
value: filteredMarkets[index].$1,
child: Text(filteredMarkets[index].$2),
);
},
);
},
).call,
); );
}, },
); );

View File

@ -16,6 +16,7 @@ import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/button/back_button.dart'; import 'package:spotube/components/button/back_button.dart';
import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/string.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/modules/library/local_folder/cache_export_dialog.dart';
import 'package:spotube/pages/library/user_local_tracks/user_local_tracks.dart'; import 'package:spotube/pages/library/user_local_tracks/user_local_tracks.dart';
import 'package:spotube/components/expandable_search/expandable_search.dart'; import 'package:spotube/components/expandable_search/expandable_search.dart';
@ -79,7 +80,7 @@ class LocalLibraryPage extends HookConsumerWidget {
final isPlaylistPlaying = playlist.containsTracks( final isPlaylistPlaying = playlist.containsTracks(
trackSnapshot.asData?.value.values.flattened.toList() ?? []); trackSnapshot.asData?.value.values.flattened.toList() ?? []);
final searchController = useTextEditingController(); final searchController = useShadcnTextEditingController();
useValueListenable(searchController); useValueListenable(searchController);
final searchFocus = useFocusNode(); final searchFocus = useFocusNode();
final isFiltering = useState(false); final isFiltering = useState(false);

View File

@ -14,6 +14,7 @@ import 'package:spotube/components/fallbacks/anonymous_fallback.dart';
import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/components/titlebar/titlebar.dart';
import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.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/albums.dart';
import 'package:spotube/pages/search/sections/artists.dart'; import 'package:spotube/pages/search/sections/artists.dart';
import 'package:spotube/pages/search/sections/playlists.dart'; import 'package:spotube/pages/search/sections/playlists.dart';
@ -35,7 +36,7 @@ class SearchPage extends HookConsumerWidget {
final mediaQuery = MediaQuery.sizeOf(context); final mediaQuery = MediaQuery.sizeOf(context);
final scrollController = useScrollController(); final scrollController = useScrollController();
final controller = useSearchController(); final controller = useShadcnTextEditingController();
final focusNode = useFocusNode(); final focusNode = useFocusNode();
final auth = ref.watch(authenticationProvider); final auth = ref.watch(authenticationProvider);
@ -120,10 +121,12 @@ class SearchPage extends HookConsumerWidget {
} }
}, },
child: AutoComplete( child: AutoComplete(
suggestions: suggestions,
child: TextField(
autofocus: true, autofocus: true,
controller: controller, controller: controller,
suggestions: suggestions, leading:
leading: const Icon(SpotubeIcons.search), const Icon(SpotubeIcons.search),
textInputAction: TextInputAction.search, textInputAction: TextInputAction.search,
placeholder: Text(context.l10n.search), placeholder: Text(context.l10n.search),
trailing: AnimatedCrossFade( trailing: AnimatedCrossFade(
@ -135,7 +138,8 @@ class SearchPage extends HookConsumerWidget {
: CrossFadeState.showSecond, : CrossFadeState.showSecond,
firstChild: IconButton.ghost( firstChild: IconButton.ghost(
size: ButtonSize.small, size: ButtonSize.small,
icon: const Icon(SpotubeIcons.close), icon:
const Icon(SpotubeIcons.close),
onPressed: () { onPressed: () {
controller.clear(); controller.clear();
}, },
@ -143,19 +147,9 @@ class SearchPage extends HookConsumerWidget {
secondChild: const SizedBox.square( secondChild: const SizedBox.square(
dimension: 28), dimension: 28),
), ),
onAcceptSuggestion: (index) {
controller.text = KVStoreService
.recentSearches[index];
ref
.read(searchTermStateProvider
.notifier)
.state =
KVStoreService
.recentSearches[index];
},
onChanged: (value) {},
onSubmitted: onSubmitted, onSubmitted: onSubmitted,
), ),
),
); );
}), }),
), ),

View File

@ -91,13 +91,18 @@ class StatsStreamFeesPage extends HookConsumerWidget {
Text(translations[value]!), Text(translations[value]!),
constraints: const BoxConstraints(maxWidth: 150), constraints: const BoxConstraints(maxWidth: 150),
popupWidthConstraint: PopoverConstraint.anchorMaxSize, popupWidthConstraint: PopoverConstraint.anchorMaxSize,
children: [ popup: SelectPopup(
for (final entry in translations.entries) items: SelectItemBuilder(
SelectItemButton( childCount: translations.length,
builder: (context, index) {
final entry = translations.entries.elementAt(index);
return SelectItemButton(
value: entry.key, value: entry.key,
child: Text(entry.value), child: Text(entry.value),
);
},
), ),
], ).call,
), ),
], ],
), ),

View File

@ -2012,10 +2012,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: shadcn_flutter name: shadcn_flutter
sha256: a04b6ce51ff8486fe9c0c3b373605ab30b823507a79a4458fc74b056edc883d8 sha256: "1e5f40484a42217a69af254952168783d1305025d56dabc45ab16396dba84d5e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.0.25" version: "0.0.26"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -104,7 +104,7 @@ dependencies:
ref: dart-3-support ref: dart-3-support
url: https://github.com/KRTirtho/scrobblenaut.git url: https://github.com/KRTirtho/scrobblenaut.git
scroll_to_index: ^3.0.1 scroll_to_index: ^3.0.1
shadcn_flutter: ^0.0.25 shadcn_flutter: ^0.0.26
shared_preferences: ^2.2.3 shared_preferences: ^2.2.3
shelf: ^1.4.1 shelf: ^1.4.1
shelf_router: ^1.1.4 shelf_router: ^1.1.4