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,
|
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) {
|
||||||
|
@ -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)
|
||||||
|
@ -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) {
|
||||||
|
@ -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: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() {
|
||||||
|
@ -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());
|
||||||
|
|
||||||
|
@ -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,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -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(
|
||||||
|
@ -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(
|
||||||
|
@ -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(
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -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,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -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);
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user