This commit is contained in:
Leon Bösche
2026-01-16 20:53:55 +01:00
parent 072564fb0f
commit c6eb497bfa
2 changed files with 143 additions and 127 deletions

View File

@@ -417,18 +417,12 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
right: 32, right: 32,
), ),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
// Title // Title
Expanded( Builder(
flex: 3,
child: Align(
alignment: Alignment.centerLeft,
child: Builder(
builder: (context) { builder: (context) {
final screenWidth = MediaQuery.of( final screenWidth = MediaQuery.of(context).size.width;
context,
).size.width;
final fontSize = screenWidth < 600 ? 24.0 : 48.0; final fontSize = screenWidth < 600 ? 24.0 : 48.0;
return Text( return Text(
'b0esche.cloud', 'b0esche.cloud',
@@ -443,21 +437,18 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
); );
}, },
), ),
), const SizedBox(width: 32),
), // Audio bar (centered between title and nav buttons)
// Audio bar (max 1/4 width, right-aligned)
Expanded( Expanded(
flex: 1,
child: AnimatedBuilder( child: AnimatedBuilder(
animation: _audioBarController, animation: _audioBarController,
builder: (context, child) { builder: (context, child) {
return (_showAudioBar && return (_showAudioBar &&
_audioFileName != null && _audioFileName != null &&
_audioFileUrl != null) _audioFileUrl != null)
? Align( ? Center(
alignment: Alignment.topRight,
child: FractionallySizedBox( child: FractionallySizedBox(
widthFactor: 1.0, widthFactor: 0.7,
child: SlideTransition( child: SlideTransition(
position: _audioBarOffset, position: _audioBarOffset,
child: AudioPlayerBar( child: AudioPlayerBar(
@@ -475,6 +466,32 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
}, },
), ),
), ),
const SizedBox(width: 32),
// Navigation buttons (Drive, Mail, Add, Profile)
BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
final isLoggedIn = state is AuthAuthenticated;
if (!isLoggedIn) {
return const SizedBox.shrink();
}
return Row(
mainAxisSize: MainAxisSize.min,
children: [
_buildNavButton('Drive', Icons.cloud),
const SizedBox(width: 16),
_buildNavButton('Mail', Icons.mail),
const SizedBox(width: 16),
_buildNavButton('Add', Icons.add),
const SizedBox(width: 16),
_buildNavButton(
'Profile',
Icons.person,
isAvatar: true,
),
],
);
},
),
], ],
), ),
), ),
@@ -729,30 +746,7 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
}, },
), ),
), ),
Positioned( // Removed duplicate title Positioned widget
top: 0,
left: 0,
right: 0,
child: Center(
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()],
),
);
},
),
),
),
Positioned( Positioned(
top: MediaQuery.of(context).size.width < 600 ? 40 : 10, top: MediaQuery.of(context).size.width < 600 ? 40 : 10,
right: 20, right: 20,

View File

@@ -20,8 +20,10 @@ class AudioPlayerBar extends StatefulWidget {
State<AudioPlayerBar> createState() => _AudioPlayerBarState(); State<AudioPlayerBar> createState() => _AudioPlayerBarState();
} }
class _AudioPlayerBarState extends State<AudioPlayerBar> { class _AudioPlayerBarState extends State<AudioPlayerBar>
with SingleTickerProviderStateMixin {
late AudioPlayer _audioPlayer; late AudioPlayer _audioPlayer;
late AnimationController _iconController;
Duration _duration = Duration.zero; Duration _duration = Duration.zero;
Duration _position = Duration.zero; Duration _position = Duration.zero;
bool _isPlaying = false; bool _isPlaying = false;
@@ -31,9 +33,20 @@ class _AudioPlayerBarState extends State<AudioPlayerBar> {
void initState() { void initState() {
super.initState(); super.initState();
_audioPlayer = AudioPlayer(); _audioPlayer = AudioPlayer();
_iconController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 250),
);
_initAudio(); _initAudio();
} }
@override
void dispose() {
_iconController.dispose();
_audioPlayer.dispose();
super.dispose();
}
Future<void> _initAudio() async { Future<void> _initAudio() async {
try { try {
await _audioPlayer.setUrl(widget.fileUrl); await _audioPlayer.setUrl(widget.fileUrl);
@@ -55,58 +68,61 @@ class _AudioPlayerBarState extends State<AudioPlayerBar> {
setState(() { setState(() {
_isLoading = false; _isLoading = false;
}); });
// Optionally show error }
}
void _handlePlayPause() {
if (_isPlaying) {
_audioPlayer.pause();
_iconController.reverse();
} else {
_audioPlayer.play();
_iconController.forward();
} }
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Sync icon animation with state
if (_isPlaying &&
_iconController.status != AnimationStatus.forward &&
_iconController.value == 0.0) {
_iconController.forward();
} else if (!_isPlaying &&
_iconController.status != AnimationStatus.reverse &&
_iconController.value == 1.0) {
_iconController.reverse();
}
return AnimatedContainer( return AnimatedContainer(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
height: 64, height: 48,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: AppTheme.glassDecoration.copyWith( decoration: AppTheme.glassDecoration.copyWith(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(24),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: AppTheme.accentColor.withOpacity(0.18), color: AppTheme.accentColor.withValues(alpha: 0.15),
blurRadius: 16, blurRadius: 12,
offset: const Offset(0, 4), offset: const Offset(0, 2),
), ),
], ],
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
// Animated play/pause button // Play/Pause button (AnimatedIcon)
Padding( ModernGlassButton(
padding: const EdgeInsets.symmetric(horizontal: 8.0), onPressed: _isLoading ? () {} : _handlePlayPause,
child: ModernGlassButton(
onPressed: _isLoading child: AnimatedIcon(
? () {} // no-op when loading icon: AnimatedIcons.play_pause,
: () { progress: _iconController,
if (_isPlaying) {
_audioPlayer.pause();
} else {
_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, color: AppTheme.primaryText,
size: 22,
), ),
), ),
), const SizedBox(width: 10),
),
// File name and slider // File name and slider
Expanded( Expanded(
child: Column( child: Column(
@@ -118,19 +134,23 @@ class _AudioPlayerBarState extends State<AudioPlayerBar> {
style: const TextStyle( style: const TextStyle(
color: AppTheme.primaryText, color: AppTheme.primaryText,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
fontSize: 15, fontSize: 14,
), ),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
SliderTheme( SizedBox(
height: 18,
child: SliderTheme(
data: SliderTheme.of(context).copyWith( data: SliderTheme.of(context).copyWith(
trackHeight: 3, trackHeight: 2.2,
thumbShape: const RoundSliderThumbShape( thumbShape: const RoundSliderThumbShape(
enabledThumbRadius: 7, enabledThumbRadius: 6,
), ),
overlayShape: SliderComponentShape.noOverlay, overlayShape: SliderComponentShape.noOverlay,
activeTrackColor: AppTheme.accentColor, activeTrackColor: AppTheme.accentColor,
inactiveTrackColor: AppTheme.accentColor.withOpacity(0.2), inactiveTrackColor: AppTheme.accentColor.withValues(
alpha: 0.18,
),
), ),
child: Slider( child: Slider(
min: 0, min: 0,
@@ -146,34 +166,36 @@ class _AudioPlayerBarState extends State<AudioPlayerBar> {
); );
}, },
activeColor: AppTheme.accentColor, activeColor: AppTheme.accentColor,
inactiveColor: AppTheme.accentColor.withOpacity(0.2), inactiveColor: AppTheme.accentColor.withValues(
alpha: 0.18,
),
),
), ),
), ),
], ],
), ),
), ),
const SizedBox(width: 10),
// Time // Time
Padding( Text(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text(
'${_formatDuration(_position)} / ${_formatDuration(_duration)}', '${_formatDuration(_position)} / ${_formatDuration(_duration)}',
style: const TextStyle( style: const TextStyle(
color: AppTheme.secondaryText, color: AppTheme.secondaryText,
fontSize: 13, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
), // Close button (smaller)
// Close button
if (widget.onClose != null) if (widget.onClose != null)
Padding( Padding(
padding: const EdgeInsets.only(right: 8.0), padding: const EdgeInsets.only(left: 8.0),
child: ModernGlassButton( child: ModernGlassButton(
onPressed: widget.onClose!, onPressed: widget.onClose!,
child: const Icon( child: const Icon(
Icons.close, Icons.close,
color: AppTheme.primaryText, color: AppTheme.primaryText,
size: 20, size: 18,
), ),
), ),
), ),