refactor: use shadcn CardImage for playbutton card

This commit is contained in:
Kingkor Roy Tirtho 2024-12-21 15:21:13 +06:00
parent 2488da2279
commit 1089e90511
2 changed files with 65 additions and 174 deletions

View File

@ -2,6 +2,7 @@ import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:skeletonizer/skeletonizer.dart'; import 'package:skeletonizer/skeletonizer.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/collections/fake.dart'; import 'package:spotube/collections/fake.dart';
@ -90,6 +91,7 @@ class HorizontalPlaybuttonCardView<T> extends HookWidget {
), ),
isLoading: isLoadingNextPage, isLoading: isLoadingNextPage,
hasReachedMax: !hasNextPage, hasReachedMax: !hasNextPage,
separatorBuilder: (context, index) => const Gap(8.0),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final item = items[index]; final item = items[index];

View File

@ -1,16 +1,9 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:skeletonizer/skeletonizer.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/hover_builder.dart';
import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/image/universal_image.dart';
import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/extensions/string.dart'; import 'package:spotube/extensions/string.dart';
import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
import 'package:spotube/hooks/utils/use_brightness_value.dart';
class PlaybuttonCard extends HookWidget { class PlaybuttonCard extends HookWidget {
final void Function()? onTap; final void Function()? onTap;
@ -40,180 +33,76 @@ class PlaybuttonCard extends HookWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textsKey = useMemoized(() => GlobalKey(), []);
final theme = Theme.of(context);
final mediaQuery = MediaQuery.of(context);
final radius = BorderRadius.circular(15);
final double size = useBreakpointValue<double>(
xs: 130,
sm: 130,
md: 150,
others: 170,
);
final end = useBreakpointValue<double>(
xs: 7,
sm: 7,
others: 15,
);
final unescapeHtml = description?.unescapeHtml().cleanHtml(); final unescapeHtml = description?.unescapeHtml().cleanHtml();
return Container( return Container(
constraints: BoxConstraints(maxWidth: size), width: 150,
margin: margin, child: CardImage(
child: Material( image: Stack(
color: Color.lerp( children: [
theme.colorScheme.surfaceContainerHighest, UniversalImage(
theme.colorScheme.surface, path: imageUrl,
useBrightnessValue(.9, .7), fit: BoxFit.cover,
), ),
borderRadius: radius, StatedWidget.builder(
shadowColor: theme.colorScheme.surface, builder: (context, states) {
elevation: 3, return Positioned(
child: InkWell( right: 8,
mouseCursor: SystemMouseCursors.click, bottom: 8,
onTap: onTap, child: Column(
borderRadius: radius, children: [
splashFactory: theme.splashFactory, AnimatedScale(
child: Column( curve: Curves.easeOutBack,
mainAxisSize: MainAxisSize.min, duration: const Duration(milliseconds: 300),
crossAxisAlignment: CrossAxisAlignment.stretch, scale: states.contains(WidgetState.hovered) ? 1 : 0.7,
children: [ child: AnimatedOpacity(
Stack( duration: const Duration(milliseconds: 300),
clipBehavior: Clip.none, opacity: states.contains(WidgetState.hovered) ? 1 : 0,
children: [ child: IconButton.secondary(
Container( icon: const Icon(SpotubeIcons.queueAdd),
margin: const EdgeInsets.fromLTRB(8, 8, 8, 0), onPressed: onAddToQueuePressed,
padding: const EdgeInsets.only( size: ButtonSize.small,
left: 8, ),
right: 8, ),
top: 8,
),
height: mediaQuery.smAndDown
? 120
: mediaQuery.mdAndDown
? 130
: 150,
decoration: BoxDecoration(
borderRadius: radius,
image: DecorationImage(
image: UniversalImage.imageProvider(imageUrl),
fit: BoxFit.cover,
), ),
), const Gap(5),
), AnimatedScale(
if (isOwner) curve: Curves.easeOutBack,
Positioned(
top: 15,
left: 15,
child: AnimatedSize(
duration: const Duration(milliseconds: 150), duration: const Duration(milliseconds: 150),
alignment: Alignment.centerLeft, scale: states.contains(WidgetState.hovered) ? 1 : 0.7,
curve: Curves.easeInExpo, child: AnimatedOpacity(
child: HoverBuilder(builder: (context, isHovered) { duration: const Duration(milliseconds: 150),
return Container( opacity: states.contains(WidgetState.hovered) ? 1 : 0,
padding: const EdgeInsets.all(4), child: IconButton.secondary(
decoration: BoxDecoration( icon: const Icon(SpotubeIcons.play),
color: Colors.blueAccent, onPressed: onPlaybuttonPressed,
borderRadius: BorderRadius.circular(20), size: ButtonSize.small,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
SpotubeIcons.user,
color: Colors.white,
size: 16,
),
if (isHovered)
Text(
context.l10n.owned_by_you,
style: theme.textTheme.bodySmall?.copyWith(
color: Colors.white,
),
),
],
),
);
}),
),
),
Positioned(
right: end,
bottom: -15,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (!isPlaying)
Skeleton.keep(
child: IconButton(
style: IconButton.styleFrom(
backgroundColor: theme.colorScheme.surface,
foregroundColor: theme.colorScheme.primary,
minimumSize: const Size.square(10),
),
icon: const Icon(SpotubeIcons.queueAdd),
onPressed: isLoading ? null : onAddToQueuePressed,
),
), ),
const Gap(5),
IconButton(
style: IconButton.styleFrom(
backgroundColor: theme.colorScheme.primaryContainer,
foregroundColor: theme.colorScheme.primary,
minimumSize: const Size.square(10),
),
icon: Skeleton.keep(
child: isLoading
? SizedBox.fromSize(
size: const Size.square(15),
child: const CircularProgressIndicator(
strokeWidth: 2),
)
: isPlaying
? const Icon(SpotubeIcons.pause)
: const Icon(SpotubeIcons.play),
),
onPressed: isLoading ? null : onPlaybuttonPressed,
), ),
],
),
),
],
),
Column(
key: textsKey,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 15),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: AutoSizeText(
title,
maxLines: 1,
minFontSize: theme.textTheme.bodyMedium!.fontSize!,
overflow: TextOverflow.ellipsis,
),
),
if (description != null)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: AutoSizeText(
unescapeHtml!,
maxLines: 2,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurface.withOpacity(.5),
),
overflow: TextOverflow.ellipsis,
), ),
), ],
const SizedBox(height: 10), ),
], );
), },
], )
],
),
title: Tooltip(
tooltip: Text(title),
child: Text(
title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
), ),
), ),
subtitle: unescapeHtml == null
? null
: Text(
unescapeHtml,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
onPressed: onTap,
), ),
); );
} }