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,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
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()],
),
);
},
),
),
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)
const SizedBox(width: 32),
// Audio bar (centered between title and nav buttons)
Expanded(
flex: 1,
child: AnimatedBuilder(
animation: _audioBarController,
builder: (context, child) {
return (_showAudioBar &&
_audioFileName != null &&
_audioFileUrl != null)
? Align(
alignment: Alignment.topRight,
? Center(
child: FractionallySizedBox(
widthFactor: 1.0,
widthFactor: 0.7,
child: SlideTransition(
position: _audioBarOffset,
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(
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()],
),
);
},
),
),
),
// Removed duplicate title Positioned widget
Positioned(
top: MediaQuery.of(context).size.width < 600 ? 40 : 10,
right: 20,

View File

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