mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 16:05:18 +00:00
fix(performance): always running marquee text causes high GPU usage #175 and UserArtist overflow on smaller displays
This commit is contained in:
parent
e48b67cd47
commit
a23ce61446
@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/components/Shared/HoverBuilder.dart';
|
||||||
import 'package:spotube/components/Shared/SpotubeMarqueeText.dart';
|
import 'package:spotube/components/Shared/SpotubeMarqueeText.dart';
|
||||||
|
|
||||||
class ArtistCard extends StatelessWidget {
|
class ArtistCard extends StatelessWidget {
|
||||||
@ -15,52 +16,57 @@ class ArtistCard extends StatelessWidget {
|
|||||||
false)
|
false)
|
||||||
? artist.images!.first.url!
|
? artist.images!.first.url!
|
||||||
: "https://avatars.dicebear.com/api/open-peeps/${artist.id}.png?b=%231ed760&r=50&flip=1&translateX=3&translateY=-6");
|
: "https://avatars.dicebear.com/api/open-peeps/${artist.id}.png?b=%231ed760&r=50&flip=1&translateX=3&translateY=-6");
|
||||||
return InkWell(
|
return SizedBox(
|
||||||
onTap: () {
|
height: 240,
|
||||||
GoRouter.of(context).push("/artist/${artist.id}");
|
width: 200,
|
||||||
},
|
child: InkWell(
|
||||||
borderRadius: BorderRadius.circular(10),
|
onTap: () {
|
||||||
child: Ink(
|
GoRouter.of(context).push("/artist/${artist.id}");
|
||||||
width: 200,
|
},
|
||||||
decoration: BoxDecoration(
|
borderRadius: BorderRadius.circular(10),
|
||||||
color: Theme.of(context).backgroundColor,
|
child: HoverBuilder(builder: (context, isHovering) {
|
||||||
borderRadius: BorderRadius.circular(8),
|
return Ink(
|
||||||
boxShadow: [
|
width: 200,
|
||||||
BoxShadow(
|
decoration: BoxDecoration(
|
||||||
blurRadius: 10,
|
color: Theme.of(context).backgroundColor,
|
||||||
offset: const Offset(0, 3),
|
borderRadius: BorderRadius.circular(8),
|
||||||
spreadRadius: 5,
|
boxShadow: [
|
||||||
color: Theme.of(context).shadowColor)
|
BoxShadow(
|
||||||
],
|
blurRadius: 10,
|
||||||
),
|
offset: const Offset(0, 3),
|
||||||
child: Padding(
|
spreadRadius: 5,
|
||||||
padding: const EdgeInsets.all(15),
|
color: Theme.of(context).shadowColor)
|
||||||
child: Column(
|
],
|
||||||
children: [
|
),
|
||||||
CircleAvatar(
|
child: Padding(
|
||||||
maxRadius: 80,
|
padding: const EdgeInsets.all(15),
|
||||||
minRadius: 20,
|
child: Column(
|
||||||
backgroundImage: backgroundImage,
|
children: [
|
||||||
|
CircleAvatar(
|
||||||
|
maxRadius: 80,
|
||||||
|
minRadius: 20,
|
||||||
|
backgroundImage: backgroundImage,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 20,
|
||||||
|
child: SpotubeMarqueeText(
|
||||||
|
text: artist.name!,
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
minStartLength: 15,
|
||||||
|
isHovering: isHovering,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Artist",
|
||||||
|
style: Theme.of(context).textTheme.subtitle1,
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
SizedBox(
|
),
|
||||||
height: 30,
|
);
|
||||||
child: artist.name!.length > 15
|
}),
|
||||||
? SpotubeMarqueeText(
|
|
||||||
text: artist.name!,
|
|
||||||
style: Theme.of(context).textTheme.headline5!,
|
|
||||||
)
|
|
||||||
: Text(
|
|
||||||
artist.name!,
|
|
||||||
style: Theme.of(context).textTheme.headline5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"Artist",
|
|
||||||
style: Theme.of(context).textTheme.subtitle1,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -31,8 +31,8 @@ class UserArtists extends HookConsumerWidget {
|
|||||||
|
|
||||||
return PagedGridView(
|
return PagedGridView(
|
||||||
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
maxCrossAxisExtent: 250,
|
maxCrossAxisExtent: 200,
|
||||||
childAspectRatio: 9 / 11,
|
mainAxisExtent: 250,
|
||||||
crossAxisSpacing: 20,
|
crossAxisSpacing: 20,
|
||||||
mainAxisSpacing: 20,
|
mainAxisSpacing: 20,
|
||||||
),
|
),
|
||||||
|
@ -157,17 +157,12 @@ class SyncedLyrics extends HookConsumerWidget {
|
|||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Center(
|
Center(
|
||||||
child: playback.track?.name != null &&
|
child: SpotubeMarqueeText(
|
||||||
playback.track!.name!.length > 29
|
text: playback.track?.name ?? "Not Playing",
|
||||||
? SpotubeMarqueeText(
|
style: headlineTextStyle,
|
||||||
text: playback.track?.name ??
|
minStartLength: 29,
|
||||||
"Not Playing",
|
isHovering: true,
|
||||||
style: headlineTextStyle,
|
),
|
||||||
)
|
|
||||||
: Text(
|
|
||||||
playback.track?.name ?? "Not Playing",
|
|
||||||
style: headlineTextStyle,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: Align(
|
child: Align(
|
||||||
|
@ -82,22 +82,16 @@ class PlayerView extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 30,
|
height: 30,
|
||||||
child: currentTrack?.name != null &&
|
child: SpotubeMarqueeText(
|
||||||
currentTrack!.name!.length > 29
|
text: currentTrack?.name ?? "Not playing",
|
||||||
? SpotubeMarqueeText(
|
style:
|
||||||
text: currentTrack.name ?? "Not playing",
|
Theme.of(context).textTheme.headline5?.copyWith(
|
||||||
style: Theme.of(context)
|
fontWeight: FontWeight.bold,
|
||||||
.textTheme
|
color: paletteColor.titleTextColor,
|
||||||
.headline5
|
),
|
||||||
?.copyWith(
|
isHovering: true,
|
||||||
fontWeight: FontWeight.bold,
|
minStartLength: 29,
|
||||||
color: paletteColor.titleTextColor,
|
),
|
||||||
),
|
|
||||||
)
|
|
||||||
: Text(
|
|
||||||
currentTrack?.name ?? "Not Playing",
|
|
||||||
style: Theme.of(context).textTheme.headline5,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
TypeConversionUtils.artists_X_ClickableArtists(
|
TypeConversionUtils.artists_X_ClickableArtists(
|
||||||
currentTrack?.artists ?? [],
|
currentTrack?.artists ?? [],
|
||||||
|
25
lib/components/Shared/HoverBuilder.dart
Normal file
25
lib/components/Shared/HoverBuilder.dart
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
|
||||||
|
class HoverBuilder extends HookWidget {
|
||||||
|
final Widget Function(BuildContext context, bool isHovering) builder;
|
||||||
|
const HoverBuilder({
|
||||||
|
required this.builder,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final hovering = useState(false);
|
||||||
|
|
||||||
|
return MouseRegion(
|
||||||
|
onEnter: (_) {
|
||||||
|
if (!hovering.value) hovering.value = true;
|
||||||
|
},
|
||||||
|
onExit: (_) {
|
||||||
|
if (hovering.value) hovering.value = false;
|
||||||
|
},
|
||||||
|
child: builder(context, hovering.value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:spotube/components/Shared/HoverBuilder.dart';
|
||||||
import 'package:spotube/components/Shared/SpotubeMarqueeText.dart';
|
import 'package:spotube/components/Shared/SpotubeMarqueeText.dart';
|
||||||
|
|
||||||
class PlaybuttonCard extends StatelessWidget {
|
class PlaybuttonCard extends StatelessWidget {
|
||||||
@ -32,120 +33,109 @@ class PlaybuttonCard extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 200),
|
constraints: const BoxConstraints(maxWidth: 200),
|
||||||
child: Ink(
|
child: HoverBuilder(builder: (context, isHovering) {
|
||||||
decoration: BoxDecoration(
|
return Ink(
|
||||||
color: Theme.of(context).backgroundColor,
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(8),
|
color: Theme.of(context).backgroundColor,
|
||||||
boxShadow: [
|
borderRadius: BorderRadius.circular(8),
|
||||||
BoxShadow(
|
boxShadow: [
|
||||||
blurRadius: 10,
|
BoxShadow(
|
||||||
offset: const Offset(0, 3),
|
blurRadius: 10,
|
||||||
spreadRadius: 5,
|
offset: const Offset(0, 3),
|
||||||
color: Theme.of(context).shadowColor,
|
spreadRadius: 5,
|
||||||
)
|
color: Theme.of(context).shadowColor,
|
||||||
],
|
)
|
||||||
),
|
],
|
||||||
child: Column(
|
),
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
child: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
// thumbnail of the playlist
|
children: [
|
||||||
Stack(
|
// thumbnail of the playlist
|
||||||
children: [
|
Stack(
|
||||||
ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
child: CachedNetworkImage(
|
|
||||||
imageUrl: imageUrl,
|
|
||||||
placeholder: (context, url) =>
|
|
||||||
Image.asset("assets/placeholder.png"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned.directional(
|
|
||||||
textDirection: TextDirection.ltr,
|
|
||||||
bottom: 10,
|
|
||||||
end: 5,
|
|
||||||
child: Builder(builder: (context) {
|
|
||||||
return ElevatedButton(
|
|
||||||
onPressed: onPlaybuttonPressed,
|
|
||||||
child: isLoading
|
|
||||||
? const SizedBox(
|
|
||||||
height: 23,
|
|
||||||
width: 23,
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
)
|
|
||||||
: Icon(
|
|
||||||
isPlaying
|
|
||||||
? Icons.pause_rounded
|
|
||||||
: Icons.play_arrow_rounded,
|
|
||||||
),
|
|
||||||
style: ButtonStyle(
|
|
||||||
shape: MaterialStateProperty.all(
|
|
||||||
const CircleBorder(),
|
|
||||||
),
|
|
||||||
padding: MaterialStateProperty.all(
|
|
||||||
const EdgeInsets.all(16),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Padding(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
children: [
|
||||||
Tooltip(
|
ClipRRect(
|
||||||
message: title,
|
borderRadius: BorderRadius.circular(8),
|
||||||
child: SizedBox(
|
child: CachedNetworkImage(
|
||||||
height: 20,
|
imageUrl: imageUrl,
|
||||||
child: title.length > 25
|
placeholder: (context, url) =>
|
||||||
? SpotubeMarqueeText(
|
Image.asset("assets/placeholder.png"),
|
||||||
text: title,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold),
|
|
||||||
)
|
|
||||||
: Text(
|
|
||||||
title,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (description != null) ...[
|
Positioned.directional(
|
||||||
const SizedBox(height: 10),
|
textDirection: TextDirection.ltr,
|
||||||
SizedBox(
|
bottom: 10,
|
||||||
height: 30,
|
end: 5,
|
||||||
child: description!.length > 30
|
child: Builder(builder: (context) {
|
||||||
? SpotubeMarqueeText(
|
return ElevatedButton(
|
||||||
text: description!,
|
onPressed: onPlaybuttonPressed,
|
||||||
style: TextStyle(
|
child: isLoading
|
||||||
fontSize: 13,
|
? const SizedBox(
|
||||||
color: Theme.of(context)
|
height: 23,
|
||||||
.textTheme
|
width: 23,
|
||||||
.headline4
|
child: CircularProgressIndicator(),
|
||||||
?.color,
|
)
|
||||||
|
: Icon(
|
||||||
|
isPlaying
|
||||||
|
? Icons.pause_rounded
|
||||||
|
: Icons.play_arrow_rounded,
|
||||||
),
|
),
|
||||||
)
|
style: ButtonStyle(
|
||||||
: Text(
|
shape: MaterialStateProperty.all(
|
||||||
description!,
|
const CircleBorder(),
|
||||||
style: TextStyle(
|
),
|
||||||
fontSize: 13,
|
padding: MaterialStateProperty.all(
|
||||||
color: Theme.of(context)
|
const EdgeInsets.all(16),
|
||||||
.textTheme
|
),
|
||||||
.headline4
|
),
|
||||||
?.color,
|
);
|
||||||
),
|
}),
|
||||||
),
|
)
|
||||||
),
|
|
||||||
]
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 5),
|
||||||
],
|
Padding(
|
||||||
),
|
padding: const EdgeInsets.symmetric(
|
||||||
),
|
horizontal: 16, vertical: 10),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Tooltip(
|
||||||
|
message: title,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 20,
|
||||||
|
child: SpotubeMarqueeText(
|
||||||
|
text: title,
|
||||||
|
style:
|
||||||
|
const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
minStartLength: 25,
|
||||||
|
isHovering: isHovering,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (description != null) ...[
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
SizedBox(
|
||||||
|
height: 30,
|
||||||
|
child: SpotubeMarqueeText(
|
||||||
|
text: description!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.headline4
|
||||||
|
?.color,
|
||||||
|
),
|
||||||
|
isHovering: isHovering,
|
||||||
|
minStartLength: 30,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,29 +1,63 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:marquee/marquee.dart';
|
import 'package:marquee/marquee.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
class SpotubeMarqueeText extends StatelessWidget {
|
class SpotubeMarqueeText extends HookWidget {
|
||||||
const SpotubeMarqueeText({Key? key, required this.text, this.style})
|
final int? minStartLength;
|
||||||
: super(key: key);
|
final bool? isHovering;
|
||||||
|
const SpotubeMarqueeText({
|
||||||
|
Key? key,
|
||||||
|
required this.text,
|
||||||
|
this.style,
|
||||||
|
this.minStartLength,
|
||||||
|
this.isHovering,
|
||||||
|
}) : super(key: key);
|
||||||
final TextStyle? style;
|
final TextStyle? style;
|
||||||
final String text;
|
final String text;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final hovering = useState(false);
|
||||||
|
final isInitial = useState(true);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
if (isHovering != null && isHovering != hovering.value) {
|
||||||
|
hovering.value = isHovering!;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [isHovering]);
|
||||||
|
|
||||||
|
if ((!isInitial.value && !hovering.value && kIsDesktop) ||
|
||||||
|
minStartLength != null && text.length <= minStartLength!) {
|
||||||
|
return Text(
|
||||||
|
text,
|
||||||
|
style: style,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return Marquee(
|
return Marquee(
|
||||||
text: text,
|
text: text,
|
||||||
style: style,
|
style: style,
|
||||||
scrollAxis: Axis.horizontal,
|
scrollAxis: Axis.horizontal,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
blankSpace: 60.0,
|
blankSpace: 40.0,
|
||||||
velocity: 30.0,
|
velocity: 30.0,
|
||||||
startAfter: const Duration(seconds: 2),
|
|
||||||
pauseAfterRound: const Duration(seconds: 2),
|
|
||||||
accelerationDuration: const Duration(seconds: 1),
|
accelerationDuration: const Duration(seconds: 1),
|
||||||
accelerationCurve: Curves.linear,
|
accelerationCurve: Curves.linear,
|
||||||
decelerationDuration: const Duration(milliseconds: 500),
|
decelerationDuration: const Duration(milliseconds: 500),
|
||||||
decelerationCurve: Curves.easeOut,
|
decelerationCurve: Curves.easeOut,
|
||||||
fadingEdgeStartFraction: 0.15,
|
fadingEdgeStartFraction: 0.15,
|
||||||
fadingEdgeEndFraction: 0.15,
|
fadingEdgeEndFraction: 0.15,
|
||||||
|
showFadingOnlyWhenScrolling: true,
|
||||||
|
onDone: () {
|
||||||
|
if (isInitial.value) {
|
||||||
|
isInitial.value = false;
|
||||||
|
hovering.value = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
numberOfRounds: hovering.value ? null : 1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user