mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
chore: upgrade to latest working shadcn_flutter
This commit is contained in:
parent
30bf0bed62
commit
7eb0e69dd7
@ -54,7 +54,16 @@ class AdaptiveSelectTile<T> 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) {
|
||||
|
@ -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<TextInputFormatter>? 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)
|
||||
|
@ -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) {
|
||||
|
@ -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';
|
||||
}
|
@ -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() {
|
||||
|
@ -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<T extends Object> extends HookWidget {
|
||||
useValueListenable(seeds);
|
||||
final theme = Theme.of(context);
|
||||
final mediaQuery = MediaQuery.of(context);
|
||||
final seedController = useTextEditingController();
|
||||
final seedController = useShadcnTextEditingController();
|
||||
|
||||
final containerKey = useRef(GlobalKey());
|
||||
|
||||
|
@ -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,
|
||||
);
|
||||
|
||||
|
@ -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<FormBuilderState>(), []);
|
||||
|
||||
return AlertDialog(
|
||||
|
@ -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(
|
||||
|
@ -30,24 +30,30 @@ class StatsPageTopSection extends HookConsumerWidget {
|
||||
};
|
||||
|
||||
final dropdown = Select<HistoryDuration>(
|
||||
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(
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -193,16 +193,11 @@ class PlaylistGeneratorPage extends HookConsumerWidget {
|
||||
|
||||
final genreSelector = MultiSelect<String>(
|
||||
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 ?? <String>[])
|
||||
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,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
),
|
||||
);
|
||||
}),
|
||||
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user