Refactor HomePage layout to improve audio player integration and enhance UI responsiveness

This commit is contained in:
Leon Bösche
2026-01-16 16:09:07 +01:00
parent 0b2a9bad2f
commit 072564fb0f
2 changed files with 169 additions and 73 deletions

View File

@@ -405,35 +405,78 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
backgroundColor: AppTheme.primaryBackground,
body: Stack(
children: [
// Audio bar between title and button row
// Title and audio bar row
Positioned(
top: MediaQuery.of(context).size.width < 600 ? 36 : 60,
top: 0,
left: 0,
right: 0,
child: AnimatedBuilder(
animation: _audioBarController,
builder: (context, child) {
return (_showAudioBar &&
_audioFileName != null &&
_audioFileUrl != null)
? SlideTransition(
position: _audioBarOffset,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 32.0,
),
child: AudioPlayerBar(
fileName: _audioFileName!,
fileUrl: _audioFileUrl!,
onClose: () {
_audioBarController.reverse();
setState(() => _showAudioBar = false);
},
),
),
)
: const SizedBox.shrink();
},
child: Padding(
padding: EdgeInsets.only(
top: MediaQuery.of(context).size.width < 600 ? 16 : 24,
left: 32,
right: 32,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Title
Expanded(
flex: 3,
child: Align(
alignment: Alignment.centerLeft,
child: Builder(
builder: (context) {
final screenWidth = MediaQuery.of(
context,
).size.width;
final fontSize = screenWidth < 600 ? 24.0 : 48.0;
return Text(
'b0esche.cloud',
style: TextStyle(
fontFamily: 'PixelatedElegance',
fontSize: fontSize,
color: AppTheme.primaryText,
decoration: TextDecoration.underline,
decorationColor: AppTheme.primaryText,
fontFeatures: const [FontFeature.slashedZero()],
),
);
},
),
),
),
// Audio bar (max 1/4 width, right-aligned)
Expanded(
flex: 1,
child: AnimatedBuilder(
animation: _audioBarController,
builder: (context, child) {
return (_showAudioBar &&
_audioFileName != null &&
_audioFileUrl != null)
? Align(
alignment: Alignment.topRight,
child: FractionallySizedBox(
widthFactor: 1.0,
child: SlideTransition(
position: _audioBarOffset,
child: AudioPlayerBar(
fileName: _audioFileName!,
fileUrl: _audioFileUrl!,
onClose: () {
_audioBarController.reverse();
setState(() => _showAudioBar = false);
},
),
),
),
)
: const SizedBox.shrink();
},
),
),
],
),
),
),
Center(

View File

@@ -1,4 +1,7 @@
import 'package:flutter/material.dart';
import '../theme/app_theme.dart';
import '../theme/modern_glass_button.dart';
import 'package:just_audio/just_audio.dart';
class AudioPlayerBar extends StatefulWidget {
@@ -56,34 +59,29 @@ class _AudioPlayerBarState extends State<AudioPlayerBar> {
}
}
@override
void dispose() {
_audioPlayer.dispose();
super.dispose();
}
String _formatDuration(Duration d) {
String twoDigits(int n) => n.toString().padLeft(2, '0');
final minutes = twoDigits(d.inMinutes.remainder(60));
final seconds = twoDigits(d.inSeconds.remainder(60));
return '$minutes:$seconds';
}
@override
Widget build(BuildContext context) {
return Material(
elevation: 8,
color: Theme.of(context).colorScheme.surface,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
height: 64,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
IconButton(
icon: Icon(_isPlaying ? Icons.pause : Icons.play_arrow),
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
height: 64,
decoration: AppTheme.glassDecoration.copyWith(
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: AppTheme.accentColor.withOpacity(0.18),
blurRadius: 16,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
// Animated play/pause button
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: ModernGlassButton(
onPressed: _isLoading
? null
? () {} // no-op when loading
: () {
if (_isPlaying) {
_audioPlayer.pause();
@@ -91,18 +89,50 @@ class _AudioPlayerBarState extends State<AudioPlayerBar> {
_audioPlayer.play();
}
},
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 250),
transitionBuilder: (child, anim) =>
ScaleTransition(scale: anim, child: child),
child: _isPlaying
? const Icon(
Icons.pause,
key: ValueKey('pause'),
color: AppTheme.primaryText,
)
: const Icon(
Icons.play_arrow,
key: ValueKey('play'),
color: AppTheme.primaryText,
),
),
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.fileName,
style: Theme.of(context).textTheme.bodyLarge,
overflow: TextOverflow.ellipsis,
),
// File name and slider
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.fileName,
style: const TextStyle(
color: AppTheme.primaryText,
fontWeight: FontWeight.w600,
fontSize: 15,
),
Slider(
overflow: TextOverflow.ellipsis,
),
SliderTheme(
data: SliderTheme.of(context).copyWith(
trackHeight: 3,
thumbShape: const RoundSliderThumbShape(
enabledThumbRadius: 7,
),
overlayShape: SliderComponentShape.noOverlay,
activeTrackColor: AppTheme.accentColor,
inactiveTrackColor: AppTheme.accentColor.withOpacity(0.2),
),
child: Slider(
min: 0,
max: _duration.inMilliseconds.toDouble(),
value: _position.inMilliseconds
@@ -115,25 +145,48 @@ class _AudioPlayerBarState extends State<AudioPlayerBar> {
Duration(milliseconds: value.toInt()),
);
},
activeColor: AppTheme.accentColor,
inactiveColor: AppTheme.accentColor.withOpacity(0.2),
),
],
),
],
),
),
// Time
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text(
'${_formatDuration(_position)} / ${_formatDuration(_duration)}',
style: const TextStyle(
color: AppTheme.secondaryText,
fontSize: 13,
fontWeight: FontWeight.w500,
),
),
),
// Close button
if (widget.onClose != null)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text(
'${_formatDuration(_position)} / ${_formatDuration(_duration)}',
style: Theme.of(context).textTheme.bodySmall,
padding: const EdgeInsets.only(right: 8.0),
child: ModernGlassButton(
onPressed: widget.onClose!,
child: const Icon(
Icons.close,
color: AppTheme.primaryText,
size: 20,
),
),
),
if (widget.onClose != null)
IconButton(
icon: const Icon(Icons.close),
onPressed: widget.onClose,
),
],
),
],
),
);
}
// Utility to format duration
String _formatDuration(Duration d) {
String twoDigits(int n) => n.toString().padLeft(2, '0');
final minutes = twoDigits(d.inMinutes.remainder(60));
final seconds = twoDigits(d.inSeconds.remainder(60));
return '$minutes:$seconds';
}
}