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

@ -30,24 +30,30 @@ class StatsPageTopSection extends HookConsumerWidget {
}; };
final dropdown = Select<HistoryDuration>( final dropdown = Select<HistoryDuration>(
popupConstraints: const BoxConstraints(maxWidth: 150), popupConstraints: const BoxConstraints(maxWidth: 150),
popupWidthConstraint: PopoverConstraint.flexible, popupWidthConstraint: PopoverConstraint.flexible,
padding: const EdgeInsets.all(4), padding: const EdgeInsets.all(4),
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
value: historyDuration, value: historyDuration,
onChanged: (value) { onChanged: (value) {
if (value == null) return; if (value == null) return;
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(
value: item, childCount: HistoryDuration.values.length,
child: Text(translations[item]!), builder: (context, index) {
), final item = HistoryDuration.values[index];
], return SelectItemButton(
); value: item,
child: Text(translations[item]!),
);
},
),
);
});
return SliverLayoutBuilder(builder: (context, constraints) { return SliverLayoutBuilder(builder: (context, constraints) {
return SliverMainAxisGroup( return SliverMainAxisGroup(

View File

@ -84,20 +84,28 @@ 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(
value: source, childCount: AudioSource.values.length,
child: Row( builder: (context, index) {
mainAxisSize: MainAxisSize.min, final source = AudioSource.values[index];
spacing: 6,
children: [ return SelectItemButton(
audioSourceToIconMap[source]!, value: source,
Text(source.name.capitalize()), child: Row(
], mainAxisSize: MainAxisSize.min,
), spacing: 6,
children: [
audioSourceToIconMap[source]!,
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,
), ),
searchPlaceholder: Text(context.l10n.search), popup: SelectPopup.builder(
searchFilter: (item, query) { searchPlaceholder: Text(context.l10n.search),
final market = spotifyMarkets builder: (context, searchQuery) {
.firstWhere((element) => element.$1 == item) final filteredMarkets = searchQuery == null ||
.$2 searchQuery.isEmpty
.toLowerCase(); ? spotifyMarkets
: spotifyMarkets
return market.contains(query.toLowerCase()) ? 1 : 0; .where(
}, (element) =>
children: [ filterMarkets(element.$1, searchQuery),
for (final market in spotifyMarkets) )
SelectItemButton( .toList();
value: market.$1, return SelectItemBuilder(
child: Text(market.$2), childCount: filteredMarkets.length,
), builder: (context, index) {
], final market = filteredMarkets[index];
return SelectItemButton(
value: market.$1,
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(),
), ),
searchPlaceholder: Text(context.l10n.search), popup: SelectPopup.builder(
searchFilter: (locale, query) { searchPlaceholder: Text(context.l10n.search),
final language = LanguageLocals.getDisplayLanguage( builder: (context, searchQuery) {
locale.languageCode) final filteredLocale = searchQuery?.isNotEmpty != true
.toString(); ? L10n.all
: 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: [ value: const Locale("system", "system"),
SelectItemButton( child: Text(context.l10n.system_default),
value: const Locale("system", "system"), );
child: Text(context.l10n.system_default), }
),
for (final locale in L10n.all) final indexThen = searchQuery?.isNotEmpty != true
SelectItemButton( ? index
value: locale, : index - 1;
child: Text(
LanguageLocals.getDisplayLanguage( final locale = filteredLocale[indexThen];
locale.languageCode) return SelectItemButton(
.toString(), value: locale,
), child: Text(
), LanguageLocals.getDisplayLanguage(
], locale.languageCode)
.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) {
value: option, final filteredGenres = searchQuery?.isNotEmpty != true
child: Text(option), ? 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( 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(
.toList(), (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,
); );
}, },
); );

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,41 +121,34 @@ class SearchPage extends HookConsumerWidget {
} }
}, },
child: AutoComplete( child: AutoComplete(
autofocus: true,
controller: controller,
suggestions: suggestions, suggestions: suggestions,
leading: const Icon(SpotubeIcons.search), child: TextField(
textInputAction: TextInputAction.search, autofocus: true,
placeholder: Text(context.l10n.search), controller: controller,
trailing: AnimatedCrossFade( leading:
duration: const Icon(SpotubeIcons.search),
const Duration(milliseconds: 300), textInputAction: TextInputAction.search,
crossFadeState: placeholder: Text(context.l10n.search),
controller.text.isNotEmpty trailing: AnimatedCrossFade(
? CrossFadeState.showFirst duration:
: CrossFadeState.showSecond, const Duration(milliseconds: 300),
firstChild: IconButton.ghost( crossFadeState:
size: ButtonSize.small, controller.text.isNotEmpty
icon: const Icon(SpotubeIcons.close), ? CrossFadeState.showFirst
onPressed: () { : CrossFadeState.showSecond,
controller.clear(); firstChild: IconButton.ghost(
}, size: ButtonSize.small,
icon:
const Icon(SpotubeIcons.close),
onPressed: () {
controller.clear();
},
),
secondChild: const SizedBox.square(
dimension: 28),
), ),
secondChild: const SizedBox.square( onSubmitted: onSubmitted,
dimension: 28),
), ),
onAcceptSuggestion: (index) {
controller.text = KVStoreService
.recentSearches[index];
ref
.read(searchTermStateProvider
.notifier)
.state =
KVStoreService
.recentSearches[index];
},
onChanged: (value) {},
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,
value: entry.key, builder: (context, index) {
child: Text(entry.value), final entry = translations.entries.elementAt(index);
), return SelectItemButton(
], value: entry.key,
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