From 072564fb0fb300346969bcdf12b0fb43ea0f7d70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6sche?= Date: Fri, 16 Jan 2026 16:09:07 +0100 Subject: [PATCH] Refactor HomePage layout to improve audio player integration and enhance UI responsiveness --- b0esche_cloud/lib/pages/home_page.dart | 95 +++++++---- .../lib/widgets/audio_player_bar.dart | 147 ++++++++++++------ 2 files changed, 169 insertions(+), 73 deletions(-) diff --git a/b0esche_cloud/lib/pages/home_page.dart b/b0esche_cloud/lib/pages/home_page.dart index 367e577..125b207 100644 --- a/b0esche_cloud/lib/pages/home_page.dart +++ b/b0esche_cloud/lib/pages/home_page.dart @@ -405,35 +405,78 @@ class _HomePageState extends State 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( diff --git a/b0esche_cloud/lib/widgets/audio_player_bar.dart b/b0esche_cloud/lib/widgets/audio_player_bar.dart index e55a5c9..2d61472 100644 --- a/b0esche_cloud/lib/widgets/audio_player_bar.dart +++ b/b0esche_cloud/lib/widgets/audio_player_bar.dart @@ -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 { } } - @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 { _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 { 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'; + } }