diff --git a/lib/components/player/player_controls.dart b/lib/components/player/player_controls.dart index af12f7ca..b381b811 100644 --- a/lib/components/player/player_controls.dart +++ b/lib/components/player/player_controls.dart @@ -70,6 +70,18 @@ class PlayerControls extends HookConsumerWidget { minimumSize: const Size(28, 28), ); + final accentColor = palette?.lightVibrantColor ?? + palette?.darkVibrantColor ?? + dominantColor; + + final resumePauseStyle = IconButton.styleFrom( + backgroundColor: accentColor?.color ?? theme.colorScheme.primary, + foregroundColor: + accentColor?.titleTextColor ?? theme.colorScheme.onPrimary, + padding: const EdgeInsets.all(12), + iconSize: 24, + ); + return GestureDetector( behavior: HitTestBehavior.translucent, onTap: () { @@ -199,14 +211,7 @@ class PlayerControls extends HookConsumerWidget { : Icon( playing ? SpotubeIcons.pause : SpotubeIcons.play, ), - style: IconButton.styleFrom( - backgroundColor: - dominantColor?.color ?? theme.colorScheme.primary, - foregroundColor: dominantColor?.titleTextColor ?? - theme.colorScheme.onPrimary, - padding: const EdgeInsets.all(12), - iconSize: 24, - ), + style: resumePauseStyle, onPressed: Actions.handler( context, PlayPauseIntent(ref), diff --git a/lib/components/shared/animated_gradient.dart b/lib/components/shared/animated_gradient.dart new file mode 100644 index 00000000..2da15b60 --- /dev/null +++ b/lib/components/shared/animated_gradient.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; + +class AnimateGradient extends HookWidget { + const AnimateGradient({ + Key? key, + required this.primaryColors, + required this.secondaryColors, + this.child, + this.primaryBegin, + this.primaryEnd, + this.secondaryBegin, + this.secondaryEnd, + AnimationController? controller, + this.duration = const Duration(seconds: 4), + this.animateAlignments = true, + this.reverse = true, + }) : assert(primaryColors.length >= 2), + assert(primaryColors.length == secondaryColors.length), + _controller = controller, + super(key: key); + + /// [controller]: pass this to have a fine control over the [Animation] + final AnimationController? _controller; + + /// [duration]: Time to switch between [Gradient]. + /// By default its value is [Duration(seconds:4)] + final Duration duration; + + /// [primaryColors]: These will be the starting colors of the [Animation]. + final List primaryColors; + + /// [secondaryColors]: These Colors are those in which the [primaryColors] will transition into. + final List secondaryColors; + + /// [primaryBegin]: This is begin [Alignment] for [primaryColors]. + /// By default its value is [Alignment.topLeft] + final Alignment? primaryBegin; + + /// [primaryBegin]: This is end [Alignment] for [primaryColors]. + /// By default its value is [Alignment.topRight] + final Alignment? primaryEnd; + + /// [secondaryBegin]: This is begin [Alignment] for [secondaryColors]. + /// By default its value is [Alignment.bottomLeft] + final Alignment? secondaryBegin; + + /// [secondaryEnd]: This is end [Alignment] for [secondaryColors]. + /// By default its value is [Alignment.bottomRight] + final Alignment? secondaryEnd; + + /// [animateAlignments]: set to false if you don't want to animate the alignments. + /// This can provide you way cooler animations + final bool animateAlignments; + + /// [reverse]: set it to false if you don't want to reverse the animation. + /// using that it will go into one direction only + final bool reverse; + + final Widget? child; + + @override + Widget build(BuildContext context) { + // ignore: no_leading_underscores_for_local_identifiers + final __controller = useAnimationController( + duration: duration, + )..repeat(reverse: reverse); + + final controller = _controller ?? __controller; + + final animation = useMemoized( + () => CurvedAnimation( + parent: controller, + curve: Curves.easeInOut, + ), + [controller]); + + final colorTween = useMemoized( + () => primaryColors.map((color) { + return ColorTween( + begin: color, + end: color, + ); + }).toList(), + [primaryColors]); + final colors = useMemoized( + () => colorTween.map((color) { + return color.evaluate(animation)!; + }).toList(), + [colorTween, animation]); + + final begin = useMemoized( + () => AlignmentTween( + begin: primaryBegin ?? Alignment.topLeft, + end: primaryEnd ?? Alignment.topRight, + ), + [primaryBegin, primaryEnd]); + + final end = useMemoized( + () => AlignmentTween( + begin: secondaryBegin ?? Alignment.bottomLeft, + end: secondaryEnd ?? Alignment.bottomRight, + ), + [secondaryBegin, secondaryEnd]); + + return AnimatedBuilder( + animation: animation, + child: child, + builder: (BuildContext context, Widget? child) { + return Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: animateAlignments + ? begin.evaluate(animation) + : (primaryBegin as Alignment), + end: animateAlignments + ? end.evaluate(animation) + : primaryEnd as Alignment, + colors: colors, + ), + ), + child: child, + ); + }, + ); + } +} diff --git a/lib/components/shared/image/universal_image.dart b/lib/components/shared/image/universal_image.dart index 45c7a15c..7d01bc47 100644 --- a/lib/components/shared/image/universal_image.dart +++ b/lib/components/shared/image/universal_image.dart @@ -12,11 +12,13 @@ class UniversalImage extends HookWidget { final double? width; final double scale; final String? placeholder; + final BoxFit? fit; const UniversalImage({ required this.path, this.height, this.width, this.placeholder, + this.fit, this.scale = 1, Key? key, }) : super(key: key); @@ -57,6 +59,7 @@ class UniversalImage extends HookWidget { height: height, width: width, placeholder: AssetImage(placeholder ?? Assets.placeholder.path), + fit: fit, ); } else if (Uri.tryParse(path) != null && !path.startsWith("assets")) { return Image.file( @@ -66,6 +69,7 @@ class UniversalImage extends HookWidget { cacheHeight: height?.toInt(), cacheWidth: width?.toInt(), scale: scale, + fit: fit, errorBuilder: (context, error, stackTrace) { return Image.asset( placeholder ?? Assets.placeholder.path, @@ -85,6 +89,7 @@ class UniversalImage extends HookWidget { cacheHeight: height?.toInt(), cacheWidth: width?.toInt(), scale: scale, + fit: fit, errorBuilder: (context, error, stackTrace) { return Image.asset( placeholder ?? Assets.placeholder.path, @@ -105,6 +110,7 @@ class UniversalImage extends HookWidget { cacheHeight: height?.toInt(), cacheWidth: width?.toInt(), scale: scale, + fit: fit, errorBuilder: (context, error, stackTrace) { return Image.asset( placeholder ?? Assets.placeholder.path, diff --git a/lib/main.dart b/lib/main.dart index 01787e8b..e7e7d158 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -113,17 +113,6 @@ void main(List rawArgs) async { enableApplicationParameters: false, ), FileHandler(await getLogsPath(), printLogs: false), - SnackbarHandler( - const Duration(seconds: 5), - action: SnackBarAction( - label: "Dismiss", - onPressed: () { - ScaffoldMessenger.of( - Catcher.navigatorKey!.currentContext!, - ).hideCurrentSnackBar(); - }, - ), - ), ], ), releaseConfig: CatcherOptions(SilentReportMode(), [ diff --git a/lib/pages/player/player.dart b/lib/pages/player/player.dart index 878719bd..fc52decb 100644 --- a/lib/pages/player/player.dart +++ b/lib/pages/player/player.dart @@ -9,6 +9,7 @@ import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/player/player_actions.dart'; import 'package:spotube/components/player/player_controls.dart'; +import 'package:spotube/components/shared/animated_gradient.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart'; import 'package:spotube/components/shared/image/universal_image.dart'; import 'package:spotube/hooks/use_breakpoints.dart'; @@ -75,123 +76,136 @@ class PlayerView extends HookConsumerWidget { leading: const BackButton(), ), extendBodyBehindAppBar: true, - body: Container( - decoration: BoxDecoration( - color: palette.dominantColor?.color, - gradient: LinearGradient( - colors: [ - palette.dominantColor?.color ?? theme.colorScheme.primary, - palette.mutedColor?.color ?? theme.colorScheme.secondary, - ], - transform: const GradientRotation(0.5), - ), - ), - alignment: Alignment.center, - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 580), - child: SafeArea( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - DecoratedBox( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - boxShadow: const [ - BoxShadow( - color: Colors.black26, - spreadRadius: 2, - blurRadius: 10, - offset: Offset(0, 0), + body: AnimateGradient( + animateAlignments: true, + primaryBegin: Alignment.topLeft, + primaryEnd: Alignment.bottomLeft, + secondaryBegin: Alignment.bottomRight, + secondaryEnd: Alignment.topRight, + duration: const Duration(seconds: 25), + primaryColors: [ + palette.dominantColor?.color ?? theme.colorScheme.primary, + palette.mutedColor?.color ?? theme.colorScheme.secondary, + ], + secondaryColors: [ + (palette.darkVibrantColor ?? palette.lightVibrantColor)?.color ?? + theme.colorScheme.primaryContainer, + (palette.darkMutedColor ?? palette.lightMutedColor)?.color ?? + theme.colorScheme.secondaryContainer, + ], + child: Container( + alignment: Alignment.center, + width: double.infinity, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 580), + child: SafeArea( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + Container( + constraints: + const BoxConstraints(maxHeight: 300, maxWidth: 300), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + boxShadow: const [ + BoxShadow( + color: Colors.black26, + spreadRadius: 2, + blurRadius: 10, + offset: Offset(0, 0), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(20), + child: UniversalImage( + path: albumArt, + placeholder: Assets.albumPlaceholder.path, + fit: BoxFit.cover, ), - ], - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(20), - child: UniversalImage( - path: albumArt, - placeholder: Assets.albumPlaceholder.path, ), ), - ), - const SizedBox(height: 10), - Container( - padding: const EdgeInsets.symmetric(horizontal: 16), - alignment: Alignment.centerLeft, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AutoSizeText( - currentTrack?.name ?? "Not playing", - style: TextStyle( - fontSize: 20, - color: titleTextColor, + const SizedBox(height: 10), + Container( + padding: const EdgeInsets.symmetric(horizontal: 16), + alignment: Alignment.centerLeft, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AutoSizeText( + currentTrack?.name ?? "Not playing", + style: TextStyle( + fontSize: 20, + color: titleTextColor, + ), + maxLines: 1, + textAlign: TextAlign.start, ), - maxLines: 1, - textAlign: TextAlign.start, - ), - if (isLocalTrack) - Text( - TypeConversionUtils.artists_X_String( + if (isLocalTrack) + Text( + TypeConversionUtils.artists_X_String( + currentTrack?.artists ?? [], + ), + style: theme.textTheme.bodyMedium!.copyWith( + fontWeight: FontWeight.bold, + color: bodyTextColor, + ), + ) + else + TypeConversionUtils.artists_X_ClickableArtists( currentTrack?.artists ?? [], + textStyle: theme.textTheme.bodyMedium!.copyWith( + fontWeight: FontWeight.bold, + color: bodyTextColor, + ), + onRouteChange: (route) { + GoRouter.of(context).pop(); + GoRouter.of(context).push(route); + }, ), - style: theme.textTheme.bodyMedium!.copyWith( - fontWeight: FontWeight.bold, - color: bodyTextColor, - ), - ) - else - TypeConversionUtils.artists_X_ClickableArtists( - currentTrack?.artists ?? [], - textStyle: theme.textTheme.bodyMedium!.copyWith( - fontWeight: FontWeight.bold, - color: bodyTextColor, - ), - onRouteChange: (route) { - GoRouter.of(context).pop(); - GoRouter.of(context).push(route); + ], + ), + ), + const SizedBox(height: 40), + PlayerControls(palette: palette), + const Spacer(), + PlayerActions( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + floatingQueue: false, + extraActions: [ + if (auth != null) + IconButton( + tooltip: "Open Lyrics", + icon: const Icon(SpotubeIcons.music), + onPressed: () { + showModalBottomSheet( + context: context, + isDismissible: true, + enableDrag: true, + isScrollControlled: true, + backgroundColor: Colors.black38, + barrierColor: Colors.black12, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + ), + constraints: BoxConstraints( + maxHeight: + MediaQuery.of(context).size.height * + 0.8, + ), + builder: (context) => + const LyricsPage(isModal: true), + ); }, - ), + ) ], ), - ), - const SizedBox(height: 40), - PlayerControls(palette: palette), - const Spacer(), - PlayerActions( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - floatingQueue: false, - extraActions: [ - if (auth != null) - IconButton( - tooltip: "Open Lyrics", - icon: const Icon(SpotubeIcons.music), - onPressed: () { - showModalBottomSheet( - context: context, - isDismissible: true, - enableDrag: true, - isScrollControlled: true, - backgroundColor: Colors.black38, - barrierColor: Colors.black12, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), - ), - ), - constraints: BoxConstraints( - maxHeight: - MediaQuery.of(context).size.height * 0.8, - ), - builder: (context) => - const LyricsPage(isModal: true), - ); - }, - ) - ], - ), - ], + ], + ), ), ), ), diff --git a/pubspec.lock b/pubspec.lock index bc83a987..86e874cc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,23 +17,6 @@ packages: url: "https://pub.dev" source: hosted version: "5.4.0" - animate_gradient: - dependency: "direct main" - description: - path: "." - ref: "2e02ab5d1cb60fc172a5f15f6e91bd34a050af23" - resolved-ref: "2e02ab5d1cb60fc172a5f15f6e91bd34a050af23" - url: "https://github.com/Vikaskumar75/Animated-Gradient" - source: git - version: "0.0.2" - animated_gradient: - dependency: "direct main" - description: - name: animated_gradient - sha256: "9c0c52a093817ae42550e3affec6973a7bae7186d1d5d58749ca9689da3ba245" - url: "https://pub.dev" - source: hosted - version: "0.0.2" app_package_maker: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 1d62ded1..294373b0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,11 +9,6 @@ environment: sdk: ">=2.17.0 <3.0.0" dependencies: - animate_gradient: - git: - url: https://github.com/Vikaskumar75/Animated-Gradient - ref: 2e02ab5d1cb60fc172a5f15f6e91bd34a050af23 - animated_gradient: ^0.0.2 args: ^2.3.2 async: ^2.9.0 audio_service: ^0.18.9