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,47 +417,38 @@ 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, builder: (context) {
child: Align( final screenWidth = MediaQuery.of(context).size.width;
alignment: Alignment.centerLeft, final fontSize = screenWidth < 600 ? 24.0 : 48.0;
child: Builder( return Text(
builder: (context) { 'b0esche.cloud',
final screenWidth = MediaQuery.of( style: TextStyle(
context, fontFamily: 'PixelatedElegance',
).size.width; fontSize: fontSize,
final fontSize = screenWidth < 600 ? 24.0 : 48.0; color: AppTheme.primaryText,
return Text( decoration: TextDecoration.underline,
'b0esche.cloud', decorationColor: AppTheme.primaryText,
style: TextStyle( fontFeatures: const [FontFeature.slashedZero()],
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) const SizedBox(width: 32),
// Audio bar (centered between title and nav buttons)
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) { color: AppTheme.primaryText,
_audioPlayer.pause(); size: 22,
} 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,
),
),
), ),
), ),
const SizedBox(width: 10),
// File name and slider // File name and slider
Expanded( Expanded(
child: Column( child: Column(
@@ -118,62 +134,68 @@ 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(
data: SliderTheme.of(context).copyWith( height: 18,
trackHeight: 3, child: SliderTheme(
thumbShape: const RoundSliderThumbShape( data: SliderTheme.of(context).copyWith(
enabledThumbRadius: 7, trackHeight: 2.2,
thumbShape: const RoundSliderThumbShape(
enabledThumbRadius: 6,
),
overlayShape: SliderComponentShape.noOverlay,
activeTrackColor: AppTheme.accentColor,
inactiveTrackColor: AppTheme.accentColor.withValues(
alpha: 0.18,
),
),
child: Slider(
min: 0,
max: _duration.inMilliseconds.toDouble(),
value: _position.inMilliseconds
.clamp(0, _duration.inMilliseconds)
.toDouble(),
onChanged: _isLoading
? null
: (value) {
_audioPlayer.seek(
Duration(milliseconds: value.toInt()),
);
},
activeColor: AppTheme.accentColor,
inactiveColor: AppTheme.accentColor.withValues(
alpha: 0.18,
),
), ),
overlayShape: SliderComponentShape.noOverlay,
activeTrackColor: AppTheme.accentColor,
inactiveTrackColor: AppTheme.accentColor.withOpacity(0.2),
),
child: Slider(
min: 0,
max: _duration.inMilliseconds.toDouble(),
value: _position.inMilliseconds
.clamp(0, _duration.inMilliseconds)
.toDouble(),
onChanged: _isLoading
? null
: (value) {
_audioPlayer.seek(
Duration(milliseconds: value.toInt()),
);
},
activeColor: AppTheme.accentColor,
inactiveColor: AppTheme.accentColor.withOpacity(0.2),
), ),
), ),
], ],
), ),
), ),
const SizedBox(width: 10),
// Time // Time
Padding( Text(
padding: const EdgeInsets.symmetric(horizontal: 8.0), '${_formatDuration(_position)} / ${_formatDuration(_duration)}',
child: Text( style: const TextStyle(
'${_formatDuration(_position)} / ${_formatDuration(_duration)}', color: AppTheme.secondaryText,
style: const TextStyle( fontSize: 12,
color: AppTheme.secondaryText, fontWeight: FontWeight.w500,
fontSize: 13,
fontWeight: FontWeight.w500,
),
), ),
), ),
// Close button // Close button (smaller)
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,
), ),
), ),
), ),