redesign app phase 3
Build / Build for Android (push) Has been cancelled
Build / Build for iOS (push) Has been cancelled

This commit is contained in:
Zakaria
2026-05-19 16:41:18 -04:00
parent c36fc55102
commit 9eb6b5237c
20 changed files with 645 additions and 275 deletions
+157 -3
View File
@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:math';
import 'dart:ui';
@@ -68,6 +69,7 @@ class PlayerScreen extends StatelessWidget {
children: [
if (FinampSettingsHelper.finampSettings.showCoverAsPlayerBackground)
const _BlurredPlayerScreenBackground(),
const _PlayerAmbientVisualizer(),
SafeArea(
child: LayoutBuilder(
builder: (context, constraints) {
@@ -113,6 +115,158 @@ class PlayerScreen extends StatelessWidget {
}
}
class _PlayerAmbientVisualizer extends StatefulWidget {
const _PlayerAmbientVisualizer({Key? key}) : super(key: key);
@override
State<_PlayerAmbientVisualizer> createState() =>
_PlayerAmbientVisualizerState();
}
class _PlayerAmbientVisualizerState extends State<_PlayerAmbientVisualizer>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
StreamSubscription<PlaybackState>? _subscription;
bool _isPlaying = false;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1800),
);
final audioHandler = GetIt.instance<MusicPlayerBackgroundTask>();
_subscription = audioHandler.playbackState.listen((playbackState) {
if (_isPlaying == playbackState.playing) {
return;
}
setState(() {
_isPlaying = playbackState.playing;
});
if (_isPlaying) {
_controller.repeat();
} else {
_controller.stop();
}
});
}
@override
void dispose() {
_subscription?.cancel();
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return IgnorePointer(
child: AnimatedOpacity(
opacity: _isPlaying ? 1 : 0.18,
duration: const Duration(milliseconds: 450),
child: RepaintBoundary(
child: AnimatedBuilder(
animation: _controller,
builder: (context, _) {
return CustomPaint(
painter: _AmbientVisualizerPainter(
progress: _controller.value,
primary: colorScheme.primary,
secondary: colorScheme.secondary,
tertiary: colorScheme.tertiary,
),
size: Size.infinite,
);
},
),
),
),
);
}
}
class _AmbientVisualizerPainter extends CustomPainter {
const _AmbientVisualizerPainter({
required this.progress,
required this.primary,
required this.secondary,
required this.tertiary,
});
final double progress;
final Color primary;
final Color secondary;
final Color tertiary;
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width * 0.5, size.height * 0.42);
final pulse = 0.5 + sin(progress * pi * 2) * 0.5;
_paintOrb(
canvas,
center.translate(size.width * -0.22, size.height * -0.08),
size.shortestSide * (0.34 + pulse * 0.05),
primary.withOpacity(0.16),
);
_paintOrb(
canvas,
center.translate(size.width * 0.23, size.height * 0.02),
size.shortestSide * (0.30 + (1 - pulse) * 0.07),
tertiary.withOpacity(0.14),
);
final barPaint = Paint()..style = PaintingStyle.fill;
final barCount = max(16, (size.width / 28).floor());
final maxBarHeight = size.height * 0.14;
final baseline = size.height * 0.86;
final totalWidth = min(size.width * 0.72, 620.0);
final gap = totalWidth / barCount;
final startX = (size.width - totalWidth) / 2;
for (var i = 0; i < barCount; i++) {
final wave = sin(progress * pi * 2 + i * 0.62);
final alternateWave = sin(progress * pi * 4 + i * 0.31);
final normalized = (wave + alternateWave * 0.35 + 1.35) / 2.7;
final barHeight = 12 + normalized * maxBarHeight;
final x = startX + i * gap + gap * 0.22;
final width = gap * 0.48;
final rect = RRect.fromRectAndRadius(
Rect.fromLTWH(x, baseline - barHeight, width, barHeight),
Radius.circular(width),
);
barPaint.color = Color.lerp(primary, secondary, i / barCount)!
.withOpacity(0.16 + normalized * 0.26);
canvas.drawRRect(rect, barPaint);
}
}
void _paintOrb(Canvas canvas, Offset center, double radius, Color color) {
final paint = Paint()
..shader = RadialGradient(
colors: [color, Colors.transparent],
).createShader(Rect.fromCircle(center: center, radius: radius));
canvas.drawCircle(center, radius, paint);
}
@override
bool shouldRepaint(covariant _AmbientVisualizerPainter oldDelegate) {
return progress != oldDelegate.progress ||
primary != oldDelegate.primary ||
secondary != oldDelegate.secondary ||
tertiary != oldDelegate.tertiary;
}
}
class _PlayerArtworkSurface extends StatelessWidget {
const _PlayerArtworkSurface({Key? key}) : super(key: key);
@@ -250,9 +404,9 @@ class _PlayerScreenAlbumImage extends ConsumerWidget {
// We need a post frame callback because otherwise this
// widget rebuilds on the same frame
WidgetsBinding.instance.addPostFrameCallback(
(_) => ref.read(_albumImageProvider.notifier).state =
imageProvider,
),
(_) => ref.read(_albumImageProvider.notifier).state =
imageProvider,
),
// Here we awkwardly get the next 3 queue items so that we
// can precache them (so that the image is already loaded
// when the next song comes on).