Fix mobile app submodule initialization and cleanup

- Removed incorrect swingmusic_mobile directory
- Properly initialized swingmusic_mobile submodule from swingmusic-mobile.git
- Clean submodule configuration for unified release workflow
This commit is contained in:
Tomas Dvorak
2026-03-18 19:32:08 +01:00
parent 9f1623bb34
commit 1d964f5ba8
159 changed files with 0 additions and 18429 deletions
@@ -1,201 +0,0 @@
import 'package:flutter/material.dart';
import '../../data/models/album_model.dart';
import '../../core/constants/app_spacing.dart';
import '../../core/themes/app_theme.dart';
class AlbumCard extends StatefulWidget {
final AlbumModel album;
final VoidCallback? onTap;
final double? width;
final double? height;
const AlbumCard({
super.key,
required this.album,
this.onTap,
this.width,
this.height,
});
@override
State<AlbumCard> createState() => _AlbumCardState();
}
class _AlbumCardState extends State<AlbumCard> {
bool _isHovered = false;
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) => setState(() => _isHovered = true),
onExit: (_) => setState(() => _isHovered = false),
child: Card(
clipBehavior: Clip.antiAlias,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: AppBorderRadius.circularLG,
),
color: _isHovered ? AppTheme.gray5 : null, // Match web client hover background
child: InkWell(
onTap: onTap,
borderRadius: AppBorderRadius.circularLG,
child: Container(
width: width ?? 160,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Album Art
Expanded(
child: Container(
width: double.infinity,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceVariant,
borderRadius: AppBorderRadius.circularLG,
),
child: ClipRRect(
borderRadius: AppBorderRadius.circularLG,
child: Stack(
children: [
if (album.image.isNotEmpty)
Image.network(
album.image,
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
errorBuilder: (context, error, stackTrace) {
return _buildDefaultAlbumArt(context);
},
)
else
_buildDefaultAlbumArt(context),
// Gradient overlay matching web client
Positioned.fill(
child: AnimatedOpacity(
opacity: _isHovered ? 1.0 : 0.0,
duration: AppSpacing.transitionNormal, // 0.25s ease from web client
child: DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Colors.black.withValues(alpha: 0.6),
Colors.transparent,
],
stops: const [0.0, 0.8],
),
),
),
),
),
// Play button overlay matching web client PlayBtn.vue exactly
Positioned(
bottom: 12,
right: 12,
child: AnimatedContainer(
duration: AppSpacing.transitionNormal,
transform: Matrix4.translationValues(
0,
_isHovered ? 0 : 16, // translateY(1rem) = 16px
0,
),
child: AnimatedOpacity(
opacity: _isHovered ? 1.0 : 0.0,
duration: AppSpacing.transitionNormal,
child: Container(
width: 40, // 2.5rem = 40px
height: 40,
decoration: BoxDecoration(
color: AppTheme.darkBlue, // $darkblue exact match
shape: BoxShape.circle,
boxShadow: [
// Match web client shadow effects
BoxShadow(
color: Colors.black.withValues(alpha: 0.3),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Icon(
Icons.play_arrow,
color: AppTheme.onPrimaryColor,
size: 28, // 1.75rem = 28px
),
),
),
),
),
],
),
),
),
),
// Album Info
Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
album.displayTitle,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w700,
fontSize: 15, // 0.95rem from web client
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
album.artistNames,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.75),
fontWeight: FontWeight.w700,
fontSize: 13, // 0.8rem from web client
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (album.year.isNotEmpty) ...[
const SizedBox(height: 2),
Text(
album.year,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
fontSize: 12,
),
),
],
],
),
),
],
),
),
),
),
);
}
Widget _buildDefaultAlbumArt(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Theme.of(context).colorScheme.primary.withOpacity(0.7),
Theme.of(context).colorScheme.secondary.withOpacity(0.7),
],
),
),
child: Icon(
Icons.album,
size: 48,
color: Theme.of(context).colorScheme.onPrimary,
),
);
}
}
@@ -1,169 +0,0 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../shared/providers/audio_provider.dart';
class MiniPlayer extends StatelessWidget {
const MiniPlayer({super.key});
@override
Widget build(BuildContext context) {
return Consumer<AudioProvider>(
builder: (context, audioProvider, child) {
final currentTrack = audioProvider.currentTrack;
if (currentTrack == null) {
return const SizedBox.shrink();
}
return Container(
height: 80,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, -2),
),
],
),
child: InkWell(
onTap: () {
// Navigate to full player
Navigator.pushNamed(context, '/player');
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Row(
children: [
// Album Art
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceVariant,
),
child: currentTrack.image.isNotEmpty
? Image.network(
currentTrack.image,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return _buildDefaultArt(context);
},
)
: _buildDefaultArt(context),
),
),
const SizedBox(width: 12),
// Track Info
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
currentTrack.displayTitle,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
Text(
currentTrack.artistNames,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
// Progress Indicator
if (audioProvider.duration.inMilliseconds > 0)
SizedBox(
width: 40,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'${audioProvider.positionFormatted} / ${audioProvider.durationFormatted}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontSize: 10,
),
),
const SizedBox(height: 4),
LinearProgressIndicator(
value: audioProvider.progress,
backgroundColor: Theme.of(context).colorScheme.surfaceVariant,
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).colorScheme.primary,
),
),
],
),
),
const SizedBox(width: 8),
// Play/Pause Button
IconButton(
onPressed: () {
if (audioProvider.isPlaying) {
audioProvider.pause();
} else {
audioProvider.play();
}
},
icon: audioProvider.isLoading
? SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).colorScheme.primary,
),
),
)
: Icon(
audioProvider.isPlaying ? Icons.pause : Icons.play_arrow,
color: Theme.of(context).colorScheme.primary,
),
),
],
),
),
),
);
},
);
}
Widget _buildDefaultArt(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Theme.of(context).colorScheme.primary.withOpacity(0.7),
Theme.of(context).colorScheme.secondary.withOpacity(0.7),
],
),
),
child: Icon(
Icons.music_note,
size: 24,
color: Theme.of(context).colorScheme.onPrimary,
),
);
}
}
@@ -1,141 +0,0 @@
import 'package:flutter/material.dart';
import '../../data/models/track_model.dart';
class TrackListTile extends StatelessWidget {
final TrackModel track;
final VoidCallback? onTap;
final VoidCallback? onPlay;
final bool isPlaying;
final bool showAlbumArt;
final Widget? trailing;
const TrackListTile({
super.key,
required this.track,
this.onTap,
this.onPlay,
this.isPlaying = false,
this.showAlbumArt = true,
this.trailing,
});
@override
Widget build(BuildContext context) {
return ListTile(
onTap: onTap,
dense: true,
leading: showAlbumArt
? ClipRRect(
borderRadius: BorderRadius.circular(4),
child: Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceVariant,
),
child: track.image.isNotEmpty
? Image.network(
track.image,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return _buildDefaultArt(context);
},
)
: _buildDefaultArt(context),
),
)
: SizedBox(
width: 24,
child: Center(
child: isPlaying
? Icon(
Icons.equalizer,
size: 16,
color: Theme.of(context).colorScheme.primary,
)
: Text(
'${track.track}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
),
title: Text(
track.displayTitle,
style: TextStyle(
fontWeight: isPlaying ? FontWeight.w600 : FontWeight.normal,
color: isPlaying
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
track.artistNames,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (track.displayAlbum.isNotEmpty)
Text(
track.displayAlbum,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
trailing: trailing ??
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
track.durationFormatted,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(width: 8),
if (onPlay != null)
IconButton(
icon: Icon(
isPlaying ? Icons.pause : Icons.play_arrow,
color: Theme.of(context).colorScheme.primary,
),
onPressed: onPlay,
visualDensity: VisualDensity.compact,
),
],
),
);
}
Widget _buildDefaultArt(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Theme.of(context).colorScheme.primary.withOpacity(0.7),
Theme.of(context).colorScheme.secondary.withOpacity(0.7),
],
),
),
child: Icon(
Icons.music_note,
size: 24,
color: Theme.of(context).colorScheme.onPrimary,
),
);
}
}