fix: track_tile active and blacklist color, playbutton card action positioning

This commit is contained in:
Kingkor Roy Tirtho 2023-03-23 21:29:20 +06:00
parent 20c424c77f
commit 3f5a1b9587
8 changed files with 254 additions and 185 deletions

View File

@ -50,7 +50,11 @@ class UserAlbums extends HookConsumerWidget {
return const AnonymousFallback();
}
if (albumsQuery.isLoading || !albumsQuery.hasData) {
return const Center(child: ShimmerPlaybuttonCard(count: 7));
return Container(
alignment: Alignment.topLeft,
padding: const EdgeInsets.all(16.0),
child: const ShimmerPlaybuttonCard(count: 7),
);
}
return RefreshIndicator(
@ -63,6 +67,7 @@ class UserAlbums extends HookConsumerWidget {
padding: const EdgeInsets.all(8.0),
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
onChanged: (value) => searchText.value = value,

View File

@ -7,6 +7,7 @@ import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/shared/image/universal_image.dart';
import 'package:spotube/hooks/use_breakpoint_value.dart';
import 'package:spotube/hooks/use_brightness_value.dart';
import 'package:spotube/utils/platform.dart';
class PlaybuttonCard extends HookWidget {
final void Function()? onTap;
@ -34,52 +35,70 @@ class PlaybuttonCard extends HookWidget {
@override
Widget build(BuildContext context) {
final textsKey = useMemoized(() => GlobalKey(), []);
final theme = Theme.of(context);
final radius = BorderRadius.circular(15);
final double size = useBreakpointValue<double>(
sm: 130,
md: 150,
others: 170,
);
sm: 130,
md: 150,
others: 170,
) ??
170;
final end = useBreakpointValue<double>(
sm: 5,
md: 7,
others: 10,
sm: 15,
others: 20,
) ??
20;
final textsHeight = useState(
(textsKey.currentContext?.findRenderObject() as RenderBox?)
?.size
.height ??
110.00,
);
return Container(
constraints: BoxConstraints(maxWidth: size),
margin: margin,
child: Material(
color: Color.lerp(
theme.colorScheme.surfaceVariant,
theme.colorScheme.surface,
useBrightnessValue(.9, .7),
),
borderRadius: radius,
shadowColor: theme.colorScheme.background,
elevation: 3,
child: InkWell(
mouseCursor: SystemMouseCursors.click,
onTap: onTap,
borderRadius: radius,
splashFactory: theme.splashFactory,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
clipBehavior: Clip.none,
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((_) {
textsHeight.value =
(textsKey.currentContext?.findRenderObject() as RenderBox?)
?.size
.height ??
textsHeight.value;
});
return null;
}, [textsKey]);
return Stack(
children: [
Container(
constraints: BoxConstraints(maxWidth: size),
margin: margin,
child: Material(
color: Color.lerp(
theme.colorScheme.surfaceVariant,
theme.colorScheme.surface,
useBrightnessValue(.9, .7),
),
borderRadius: radius,
shadowColor: theme.colorScheme.background,
elevation: 3,
child: InkWell(
mouseCursor: SystemMouseCursors.click,
onTap: onTap,
borderRadius: radius,
splashFactory: theme.splashFactory,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(
Padding(
padding: const EdgeInsets.only(
left: 8,
right: 8,
top: 8,
),
constraints: BoxConstraints(maxHeight: size),
child: ClipRRect(
borderRadius: radius,
child: UniversalImage(
@ -88,77 +107,79 @@ class PlaybuttonCard extends HookWidget {
),
),
),
Positioned.directional(
textDirection: TextDirection.ltr,
end: end,
bottom: -size * .15,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (!isPlaying)
IconButton(
style: IconButton.styleFrom(
backgroundColor: theme.colorScheme.background,
foregroundColor: theme.colorScheme.primary,
minimumSize: const Size.square(10),
),
icon: const Icon(SpotubeIcons.queueAdd),
onPressed: isLoading ? null : onAddToQueuePressed,
),
const SizedBox(height: 5),
IconButton(
style: IconButton.styleFrom(
backgroundColor: theme.colorScheme.primaryContainer,
foregroundColor: theme.colorScheme.primary,
minimumSize: const Size.square(10),
),
icon: 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(
description!,
maxLines: 2,
style: theme.textTheme.bodySmall?.copyWith(
color:
theme.colorScheme.onSurface.withOpacity(.5),
),
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(height: 10),
],
),
],
),
const SizedBox(height: 15),
Flexible(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: AutoSizeText(
title,
maxLines: 1,
minFontSize: theme.textTheme.bodyMedium!.fontSize!,
overflow: TextOverflow.ellipsis,
),
),
),
AnimatedPositioned(
duration: const Duration(milliseconds: 300),
right: end,
bottom: textsHeight.value - (kIsMobile ? 5 : 10),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (!isPlaying)
IconButton(
style: IconButton.styleFrom(
backgroundColor: theme.colorScheme.background,
foregroundColor: theme.colorScheme.primary,
minimumSize: const Size.square(10),
),
icon: const Icon(SpotubeIcons.queueAdd),
onPressed: isLoading ? null : onAddToQueuePressed,
),
const SizedBox(height: 5),
IconButton(
style: IconButton.styleFrom(
backgroundColor: theme.colorScheme.primaryContainer,
foregroundColor: theme.colorScheme.primary,
minimumSize: const Size.square(10),
),
icon: 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,
),
if (description != null)
Flexible(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: AutoSizeText(
description!,
maxLines: 2,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurface.withOpacity(.5),
),
overflow: TextOverflow.ellipsis,
),
),
),
const SizedBox(height: 10),
],
),
),
),
],
);
}
}

View File

@ -21,12 +21,13 @@ class ShimmerArtistProfile extends HookWidget {
shimmerTheme.shimmerBackgroundColor ?? Colors.grey;
final avatarWidth = useBreakpointValue(
sm: MediaQuery.of(context).size.width * 0.80,
md: MediaQuery.of(context).size.width * 0.50,
lg: MediaQuery.of(context).size.width * 0.30,
xl: MediaQuery.of(context).size.width * 0.30,
xxl: MediaQuery.of(context).size.width * 0.30,
);
sm: MediaQuery.of(context).size.width * 0.80,
md: MediaQuery.of(context).size.width * 0.50,
lg: MediaQuery.of(context).size.width * 0.30,
xl: MediaQuery.of(context).size.width * 0.30,
xxl: MediaQuery.of(context).size.width * 0.30,
) ??
0;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,

View File

@ -51,7 +51,7 @@ class SortTracksDropdown extends StatelessWidget {
},
onSelected: onChanged,
tooltip: "Sort tracks",
child: const Icon(SpotubeIcons.sort),
icon: const Icon(SpotubeIcons.sort),
);
}
}

View File

@ -100,12 +100,13 @@ class TrackCollectionView<T> extends HookConsumerWidget {
),
),
// play playlist
IconButton(
style: IconButton.styleFrom(
backgroundColor: theme.colorScheme.primary,
ElevatedButton(
style: ElevatedButton.styleFrom(
shape: const CircleBorder(),
backgroundColor: theme.colorScheme.inversePrimary,
),
onPressed: tracksSnapshot.data != null ? onPlay : null,
icon: Icon(isPlaying ? SpotubeIcons.stop : SpotubeIcons.play),
child: Icon(isPlaying ? SpotubeIcons.stop : SpotubeIcons.play),
),
const SizedBox(width: 10),
];

View File

@ -179,18 +179,16 @@ class TrackTile extends HookConsumerWidget {
return AnimatedContainer(
duration: const Duration(milliseconds: 500),
decoration: BoxDecoration(
color: isBlackListed
? Colors.red[100]
: isActive
? theme.popupMenuTheme.color
: Colors.transparent,
borderRadius: BorderRadius.circular(isActive ? 10 : 0),
color: isActive
? theme.colorScheme.surfaceVariant.withOpacity(0.5)
: Colors.transparent,
borderRadius: BorderRadius.circular(10),
),
child: Material(
type: MaterialType.transparency,
child: Row(
children: [
if (showCheck)
if (showCheck && !isBlackListed)
Checkbox(
value: isChecked,
onChanged: (s) => onCheckChange?.call(s),
@ -222,22 +220,21 @@ class TrackTile extends HookConsumerWidget {
),
Padding(
padding: const EdgeInsets.all(8.0),
child: IconButton(
icon: Icon(
playlist?.activeTrack.id == track.value.id
? SpotubeIcons.pause
: SpotubeIcons.play,
color: Colors.white,
),
style: IconButton.styleFrom(
backgroundColor: theme.colorScheme.primary,
hoverColor: theme.colorScheme.primary.withOpacity(0.5),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.inversePrimary,
shape: const CircleBorder(),
),
onPressed: !isBlackListed
? () => onTrackPlayButtonPressed?.call(
track.value,
)
: null,
child: Icon(
playlist?.activeTrack.id == track.value.id
? SpotubeIcons.pause
: SpotubeIcons.play,
),
),
),
Expanded(

View File

@ -197,7 +197,7 @@ class TracksTableView extends HookConsumerWidget {
default:
}
},
child: const Icon(SpotubeIcons.moreVertical),
icon: const Icon(SpotubeIcons.moreVertical),
),
const SizedBox(width: 10),
],
@ -205,57 +205,81 @@ class TracksTableView extends HookConsumerWidget {
...sortedTracks.asMap().entries.map((track) {
String duration =
"${track.value.duration?.inMinutes.remainder(60)}:${PrimitiveUtils.zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}";
return InkWell(
onLongPress: () {
showCheck.value = true;
selected.value = [...selected.value, track.value.id!];
},
onTap: () {
if (showCheck.value) {
final alreadyChecked =
selected.value.contains(track.value.id);
if (alreadyChecked) {
selected.value = selected.value
.where((id) => id != track.value.id)
.toList();
} else {
selected.value = [...selected.value, track.value.id!];
}
} else {
final isBlackListed = ref.read(
BlackListNotifier.provider.select(
(blacklist) => blacklist.contains(
BlacklistedElement.track(
track.value.id!, track.value.name!),
),
),
);
if (!isBlackListed) {
onTrackPlayButtonPressed?.call(track.value);
}
}
},
child: TrackTile(
playlist,
playlistId: playlistId,
track: track,
duration: duration,
userPlaylist: userPlaylist,
isActive: playlist?.activeTrack.id == track.value.id,
onTrackPlayButtonPressed: onTrackPlayButtonPressed,
isChecked: selected.value.contains(track.value.id),
showCheck: showCheck.value,
onCheckChange: (checked) {
if (checked == true) {
selected.value = [...selected.value, track.value.id!];
} else {
selected.value = selected.value
.where((id) => id != track.value.id)
.toList();
}
},
),
);
return Consumer(builder: (context, ref, _) {
final isBlackListed = ref.watch(
BlackListNotifier.provider.select(
(blacklist) => blacklist.contains(
BlacklistedElement.track(
track.value.id!, track.value.name!),
),
),
);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: InkWell(
borderRadius: BorderRadius.circular(10),
onLongPress: isBlackListed
? null
: () {
showCheck.value = true;
selected.value = [
...selected.value,
track.value.id!
];
},
onTap: isBlackListed
? null
: () {
if (showCheck.value) {
final alreadyChecked =
selected.value.contains(track.value.id);
if (alreadyChecked) {
selected.value = selected.value
.where((id) => id != track.value.id)
.toList();
} else {
selected.value = [
...selected.value,
track.value.id!
];
}
} else {
final isBlackListed = ref.read(
BlackListNotifier.provider.select(
(blacklist) => blacklist.contains(
BlacklistedElement.track(
track.value.id!, track.value.name!),
),
),
);
if (!isBlackListed) {
onTrackPlayButtonPressed?.call(track.value);
}
}
},
child: TrackTile(
playlist,
playlistId: playlistId,
track: track,
duration: duration,
userPlaylist: userPlaylist,
isActive: playlist?.activeTrack.id == track.value.id,
onTrackPlayButtonPressed: onTrackPlayButtonPressed,
isChecked: selected.value.contains(track.value.id),
showCheck: showCheck.value,
onCheckChange: (checked) {
if (checked == true) {
selected.value = [...selected.value, track.value.id!];
} else {
selected.value = selected.value
.where((id) => id != track.value.id)
.toList();
}
},
),
),
);
});
}).toList(),
];

View File

@ -1,6 +1,6 @@
import 'package:spotube/hooks/use_breakpoints.dart';
useBreakpointValue<T>({
T useBreakpointValue<T>({
T? sm,
T? md,
T? lg,
@ -8,17 +8,37 @@ useBreakpointValue<T>({
T? xxl,
T? others,
}) {
final isSomeNull =
sm == null || md == null || lg == null || xl == null || xxl == null;
assert(
(isSomeNull && others != null) || (!isSomeNull && others == null),
'You must provide a value for all breakpoints or a default value for others',
);
final breakpoint = useBreakpoints();
if (breakpoint.isSm) {
return sm ?? others;
} else if (breakpoint.isMd) {
return md ?? others;
} else if (breakpoint.isXl) {
return xl ?? others;
} else if (breakpoint.isXxl) {
return xxl ?? others;
if (isSomeNull) {
if (breakpoint.isSm) {
return sm ?? others!;
} else if (breakpoint.isMd) {
return md ?? others!;
} else if (breakpoint.isXl) {
return xl ?? others!;
} else if (breakpoint.isXxl) {
return xxl ?? others!;
} else {
return lg ?? others!;
}
} else {
return lg ?? others;
if (breakpoint.isSm) {
return sm;
} else if (breakpoint.isMd) {
return md;
} else if (breakpoint.isXl) {
return xl;
} else if (breakpoint.isXxl) {
return xxl;
} else {
return lg;
}
}
}