refactor: use shadcn for TrackTile

This commit is contained in:
Kingkor Roy Tirtho 2025-01-08 22:16:46 +06:00
parent e54a646073
commit 88906098dd
6 changed files with 309 additions and 268 deletions

View File

@ -1,4 +1,3 @@
import 'package:flutter/material.dart' show ListTile;
import 'package:flutter_hooks/flutter_hooks.dart'; 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';
@ -21,9 +20,6 @@ class TrackPresentation extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final headerTextStyle = context.theme.typography.small.copyWith(
color: context.theme.colorScheme.mutedForeground,
);
final scrollController = useScrollController(); final scrollController = useScrollController();
final focusNode = useFocusNode(); final focusNode = useFocusNode();
final scale = context.theme.scaling; final scale = context.theme.scaling;
@ -66,10 +62,11 @@ class TrackPresentation extends HookConsumerWidget {
TrackPresentationModifiersSection( TrackPresentationModifiersSection(
focusNode: focusNode, focusNode: focusNode,
), ),
ListTile( Basic(
titleTextStyle: headerTextStyle, padding: const EdgeInsets.symmetric(
subtitleTextStyle: headerTextStyle, vertical: 8,
leadingAndTrailingTextStyle: headerTextStyle, horizontal: 16,
),
leading: constrains.mdAndUp ? const Text(" #") : null, leading: constrains.mdAndUp ? const Text(" #") : null,
title: Row( title: Row(
children: [ children: [
@ -85,7 +82,7 @@ class TrackPresentation extends HookConsumerWidget {
Text(context.l10n.duration), Text(context.l10n.duration),
], ],
), ),
), ).small().muted(),
], ],
); );
}, },

View File

@ -32,7 +32,11 @@ Future<void> Function(Track track, int index) useTrackTilePlayCallback(
ref.read(presentationStateProvider(options.collection).notifier); ref.read(presentationStateProvider(options.collection).notifier);
if (state.selectedTracks.isNotEmpty) { if (state.selectedTracks.isNotEmpty) {
if (state.selectedTracks.contains(track)) {
notifier.deselectTrack(track);
} else {
notifier.selectTrack(track); notifier.selectTrack(track);
}
return; return;
} }

View File

@ -1,8 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart' show ListTile, Material, MaterialType;
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.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';
@ -14,7 +14,9 @@ import 'package:spotube/components/image/universal_image.dart';
import 'package:spotube/components/links/artist_link.dart'; import 'package:spotube/components/links/artist_link.dart';
import 'package:spotube/components/links/link_text.dart'; import 'package:spotube/components/links/link_text.dart';
import 'package:spotube/components/track_tile/track_options.dart'; import 'package:spotube/components/track_tile/track_options.dart';
import 'package:spotube/components/ui/button_tile.dart';
import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/artist_simple.dart';
import 'package:spotube/extensions/button_variance.dart';
import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/duration.dart'; import 'package:spotube/extensions/duration.dart';
import 'package:spotube/extensions/image.dart'; import 'package:spotube/extensions/image.dart';
@ -89,13 +91,10 @@ class TrackTile extends HookConsumerWidget {
}, },
child: HoverBuilder( child: HoverBuilder(
permanentState: isSelected || constrains.smAndDown ? true : null, permanentState: isSelected || constrains.smAndDown ? true : null,
builder: (context, isHovering) => Material( builder: (context, isHovering) => ButtonTile(
type: MaterialType.transparency,
child: ListTile(
selectedColor: theme.colorScheme.primary,
selectedTileColor: theme.colorScheme.primary.withOpacity(0.1),
selected: isSelected, selected: isSelected,
onTap: () async { onPressed: () async {
if (isBlackListed) return;
try { try {
isLoading.value = true; isLoading.value = true;
await onTap?.call(); await onTap?.call();
@ -106,18 +105,12 @@ class TrackTile extends HookConsumerWidget {
} }
}, },
onLongPress: onLongPress, onLongPress: onLongPress,
enabled: !isBlackListed, style: (isBlackListed
contentPadding: EdgeInsets.zero, ? ButtonVariance.destructive
tileColor: isBlackListed ? theme.colorScheme.destructive : null, : ButtonVariance.ghost)
horizontalTitleGap: 12, .copyWith(
leadingAndTrailingTextStyle: theme.typography.normal.copyWith( padding: (context, states) =>
color: theme.colorScheme.foreground, const EdgeInsets.symmetric(vertical: 8, horizontal: 0),
),
titleTextStyle: theme.typography.normal.copyWith(
color: theme.colorScheme.foreground,
),
subtitleTextStyle: theme.typography.xSmall.copyWith(
color: theme.colorScheme.mutedForeground,
), ),
leading: Row( leading: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -140,8 +133,7 @@ class TrackTile extends HookConsumerWidget {
: SizedBox( : SizedBox(
width: 50, width: 50,
child: Padding( child: Padding(
padding: padding: const EdgeInsets.symmetric(horizontal: 6),
const EdgeInsets.symmetric(horizontal: 6),
child: Text( child: Text(
'${(index ?? 0) + 1}', '${(index ?? 0) + 1}',
maxLines: 1, maxLines: 1,
@ -174,7 +166,7 @@ class TrackTile extends HookConsumerWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: theme.borderRadiusMd, borderRadius: theme.borderRadiusMd,
color: isHovering color: isHovering
? Colors.black.withOpacity(0.4) ? Colors.black.withAlpha(102)
: Colors.transparent, : Colors.transparent,
), ),
), ),
@ -200,8 +192,8 @@ class TrackTile extends HookConsumerWidget {
const SizedBox( const SizedBox(
width: 26, width: 26,
height: 26, height: 26,
child: CircularProgressIndicator( child:
size: 1.5), CircularProgressIndicator(size: 1.5),
), ),
(_, _, true, _, _) => Icon( (_, _, true, _, _) => Icon(
SpotubeIcons.pause, SpotubeIcons.pause,
@ -233,13 +225,31 @@ class TrackTile extends HookConsumerWidget {
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
_ => LinkText( _ => Row(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Button(
style: ButtonVariance.link.copyWith(
padding: (context, states) => EdgeInsets.zero,
),
onPressed: () {
context.pushNamed(
TrackPage.name,
pathParameters: {
"id": track.id!,
},
);
},
child: Text(
track.name!, track.name!,
"/track/${track.id}",
push: true,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
),
),
],
),
}, },
), ),
if (constrains.mdAndUp) ...[ if (constrains.mdAndUp) ...[
@ -310,7 +320,6 @@ class TrackTile extends HookConsumerWidget {
), ),
), ),
), ),
),
); );
}); });
} }

View File

@ -6,7 +6,8 @@ class ButtonTile extends StatelessWidget {
final Widget? leading; final Widget? leading;
final Widget? trailing; final Widget? trailing;
final bool enabled; final bool enabled;
final void Function()? onPressed; final VoidCallback? onPressed;
final VoidCallback? onLongPress;
final bool selected; final bool selected;
final ButtonVariance style; final ButtonVariance style;
final EdgeInsets? padding; final EdgeInsets? padding;
@ -19,6 +20,7 @@ class ButtonTile extends StatelessWidget {
this.trailing, this.trailing,
this.enabled = true, this.enabled = true,
this.onPressed, this.onPressed,
this.onLongPress,
this.selected = false, this.selected = false,
this.padding, this.padding,
this.style = ButtonVariance.outline, this.style = ButtonVariance.outline,
@ -28,13 +30,17 @@ class ButtonTile extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ThemeData(:colorScheme, :typography) = Theme.of(context); final ThemeData(:colorScheme, :typography) = Theme.of(context);
return Button( return GestureDetector(
onLongPress: onLongPress,
child: Button(
enabled: enabled, enabled: enabled,
onPressed: onPressed, onPressed: onPressed,
style: style.copyWith( style: style.copyWith(
padding: padding != null ? (context, states, value) => padding! : null, padding:
padding != null ? (context, states, value) => padding! : null,
decoration: (context, states, value) { decoration: (context, states, value) {
final decoration = style.decoration(context, states) as BoxDecoration; final decoration =
style.decoration(context, states) as BoxDecoration;
if (selected) { if (selected) {
return switch (style) { return switch (style) {
@ -97,6 +103,7 @@ class ButtonTile extends StatelessWidget {
trailing: trailing, trailing: trailing,
), ),
), ),
),
); );
} }
} }

View File

@ -0,0 +1,21 @@
import 'package:shadcn_flutter/shadcn_flutter.dart';
extension CopyWithButtonVarianceExtension on ButtonVariance {
ButtonVariance copyWith({
ButtonStateProperty<EdgeInsets>? padding,
ButtonStateProperty<Decoration>? decoration,
ButtonStateProperty<MouseCursor>? mouseCursor,
ButtonStateProperty<IconThemeData>? iconTheme,
ButtonStateProperty<EdgeInsets>? margin,
ButtonStateProperty<TextStyle>? textStyle,
}) {
return ButtonVariance(
padding: padding ?? this.padding,
decoration: decoration ?? this.decoration,
mouseCursor: mouseCursor ?? this.mouseCursor,
iconTheme: iconTheme ?? this.iconTheme,
margin: margin ?? this.margin,
textStyle: textStyle ?? this.textStyle,
);
}
}

View File

@ -43,7 +43,7 @@ class HomeGenresSection extends HookConsumerWidget {
useEffect(() { useEffect(() {
int times = 0; int times = 0;
Timer.periodic( final timer = Timer.periodic(
const Duration(seconds: 5), const Duration(seconds: 5),
(timer) { (timer) {
if (times > 5 || interactedRef.value) { if (times > 5 || interactedRef.value) {
@ -57,7 +57,10 @@ class HomeGenresSection extends HookConsumerWidget {
}, },
); );
return controller.dispose; return () {
timer.cancel();
controller.dispose();
};
}, []); }, []);
return SliverList.list( return SliverList.list(