fix(performance): always running marquee text causes high GPU usage #175 and UserArtist overflow on smaller displays

This commit is contained in:
Kingkor Roy Tirtho 2022-08-18 12:15:31 +06:00
parent e48b67cd47
commit a23ce61446
7 changed files with 232 additions and 188 deletions

View File

@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/components/Shared/HoverBuilder.dart';
import 'package:spotube/components/Shared/SpotubeMarqueeText.dart';
class ArtistCard extends StatelessWidget {
@ -15,52 +16,57 @@ class ArtistCard extends StatelessWidget {
false)
? artist.images!.first.url!
: "https://avatars.dicebear.com/api/open-peeps/${artist.id}.png?b=%231ed760&r=50&flip=1&translateX=3&translateY=-6");
return InkWell(
onTap: () {
GoRouter.of(context).push("/artist/${artist.id}");
},
borderRadius: BorderRadius.circular(10),
child: Ink(
width: 200,
decoration: BoxDecoration(
color: Theme.of(context).backgroundColor,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
blurRadius: 10,
offset: const Offset(0, 3),
spreadRadius: 5,
color: Theme.of(context).shadowColor)
],
),
child: Padding(
padding: const EdgeInsets.all(15),
child: Column(
children: [
CircleAvatar(
maxRadius: 80,
minRadius: 20,
backgroundImage: backgroundImage,
return SizedBox(
height: 240,
width: 200,
child: InkWell(
onTap: () {
GoRouter.of(context).push("/artist/${artist.id}");
},
borderRadius: BorderRadius.circular(10),
child: HoverBuilder(builder: (context, isHovering) {
return Ink(
width: 200,
decoration: BoxDecoration(
color: Theme.of(context).backgroundColor,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
blurRadius: 10,
offset: const Offset(0, 3),
spreadRadius: 5,
color: Theme.of(context).shadowColor)
],
),
child: Padding(
padding: const EdgeInsets.all(15),
child: Column(
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,
)
],
),
),
),
);
}),
),
);
}

View File

@ -31,8 +31,8 @@ class UserArtists extends HookConsumerWidget {
return PagedGridView(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 250,
childAspectRatio: 9 / 11,
maxCrossAxisExtent: 200,
mainAxisExtent: 250,
crossAxisSpacing: 20,
mainAxisSpacing: 20,
),

View File

@ -157,17 +157,12 @@ class SyncedLyrics extends HookConsumerWidget {
child: Stack(
children: [
Center(
child: playback.track?.name != null &&
playback.track!.name!.length > 29
? SpotubeMarqueeText(
text: playback.track?.name ??
"Not Playing",
style: headlineTextStyle,
)
: Text(
playback.track?.name ?? "Not Playing",
style: headlineTextStyle,
),
child: SpotubeMarqueeText(
text: playback.track?.name ?? "Not Playing",
style: headlineTextStyle,
minStartLength: 29,
isHovering: true,
),
),
Positioned.fill(
child: Align(

View File

@ -82,22 +82,16 @@ class PlayerView extends HookConsumerWidget {
children: [
SizedBox(
height: 30,
child: currentTrack?.name != null &&
currentTrack!.name!.length > 29
? SpotubeMarqueeText(
text: currentTrack.name ?? "Not playing",
style: Theme.of(context)
.textTheme
.headline5
?.copyWith(
fontWeight: FontWeight.bold,
color: paletteColor.titleTextColor,
),
)
: Text(
currentTrack?.name ?? "Not Playing",
style: Theme.of(context).textTheme.headline5,
),
child: SpotubeMarqueeText(
text: currentTrack?.name ?? "Not playing",
style:
Theme.of(context).textTheme.headline5?.copyWith(
fontWeight: FontWeight.bold,
color: paletteColor.titleTextColor,
),
isHovering: true,
minStartLength: 29,
),
),
TypeConversionUtils.artists_X_ClickableArtists(
currentTrack?.artists ?? [],

View 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),
);
}
}

View File

@ -1,5 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:spotube/components/Shared/HoverBuilder.dart';
import 'package:spotube/components/Shared/SpotubeMarqueeText.dart';
class PlaybuttonCard extends StatelessWidget {
@ -32,120 +33,109 @@ class PlaybuttonCard extends StatelessWidget {
borderRadius: BorderRadius.circular(8),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 200),
child: Ink(
decoration: BoxDecoration(
color: Theme.of(context).backgroundColor,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
blurRadius: 10,
offset: const Offset(0, 3),
spreadRadius: 5,
color: Theme.of(context).shadowColor,
)
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// thumbnail of the playlist
Stack(
children: [
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(
child: HoverBuilder(builder: (context, isHovering) {
return Ink(
decoration: BoxDecoration(
color: Theme.of(context).backgroundColor,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
blurRadius: 10,
offset: const Offset(0, 3),
spreadRadius: 5,
color: Theme.of(context).shadowColor,
)
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// thumbnail of the playlist
Stack(
children: [
Tooltip(
message: title,
child: SizedBox(
height: 20,
child: title.length > 25
? SpotubeMarqueeText(
text: title,
style: const TextStyle(
fontWeight: FontWeight.bold),
)
: Text(
title,
style: const TextStyle(
fontWeight: FontWeight.bold),
),
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: CachedNetworkImage(
imageUrl: imageUrl,
placeholder: (context, url) =>
Image.asset("assets/placeholder.png"),
),
),
if (description != null) ...[
const SizedBox(height: 10),
SizedBox(
height: 30,
child: description!.length > 30
? SpotubeMarqueeText(
text: description!,
style: TextStyle(
fontSize: 13,
color: Theme.of(context)
.textTheme
.headline4
?.color,
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,
),
)
: Text(
description!,
style: TextStyle(
fontSize: 13,
color: Theme.of(context)
.textTheme
.headline4
?.color,
),
),
),
]
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: [
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,
),
),
]
],
),
),
],
),
);
}),
),
),
);

View File

@ -1,29 +1,63 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:marquee/marquee.dart';
import 'package:spotube/utils/platform.dart';
class SpotubeMarqueeText extends StatelessWidget {
const SpotubeMarqueeText({Key? key, required this.text, this.style})
: super(key: key);
class SpotubeMarqueeText extends HookWidget {
final int? minStartLength;
final bool? isHovering;
const SpotubeMarqueeText({
Key? key,
required this.text,
this.style,
this.minStartLength,
this.isHovering,
}) : super(key: key);
final TextStyle? style;
final String text;
@override
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(
text: text,
style: style,
scrollAxis: Axis.horizontal,
crossAxisAlignment: CrossAxisAlignment.start,
blankSpace: 60.0,
blankSpace: 40.0,
velocity: 30.0,
startAfter: const Duration(seconds: 2),
pauseAfterRound: const Duration(seconds: 2),
accelerationDuration: const Duration(seconds: 1),
accelerationCurve: Curves.linear,
decelerationDuration: const Duration(milliseconds: 500),
decelerationCurve: Curves.easeOut,
fadingEdgeStartFraction: 0.15,
fadingEdgeEndFraction: 0.15,
showFadingOnlyWhenScrolling: true,
onDone: () {
if (isInitial.value) {
isInitial.value = false;
hovering.value = false;
}
},
numberOfRounds: hovering.value ? null : 1,
);
}
}