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,437 +0,0 @@
import 'package:flutter/material.dart';
import '../../data/models/playlist_model.dart';
class PlaylistsScreen extends StatefulWidget {
const PlaylistsScreen({super.key});
@override
State<PlaylistsScreen> createState() => _PlaylistsScreenState();
}
class _PlaylistsScreenState extends State<PlaylistsScreen> {
List<PlaylistModel> _playlists = [];
bool _isLoading = false;
final TextEditingController _playlistNameController = TextEditingController();
final TextEditingController _searchController = TextEditingController();
@override
void initState() {
super.initState();
_loadPlaylists();
}
@override
void dispose() {
_playlistNameController.dispose();
_searchController.dispose();
super.dispose();
}
Future<void> _loadPlaylists() async {
setState(() {
_isLoading = true;
});
// Simulate API call
await Future.delayed(const Duration(seconds: 1));
setState(() {
_isLoading = false;
// Sample playlists for demo
_playlists = [
PlaylistModel(
id: '1',
name: 'My Favorites',
description: 'My favorite tracks',
trackcount: 25,
isPublic: false,
createdDate: DateTime.now().subtract(const Duration(days: 30)),
lastModified: DateTime.now().subtract(const Duration(days: 5)),
),
PlaylistModel(
id: '2',
name: 'Workout Mix',
description: 'High energy tracks for workouts',
trackcount: 18,
isPublic: false,
createdDate: DateTime.now().subtract(const Duration(days: 15)),
lastModified: DateTime.now().subtract(const Duration(days: 2)),
),
PlaylistModel(
id: '3',
name: 'Chill Vibes',
description: 'Relaxing and focus music',
trackcount: 32,
isPublic: true,
createdDate: DateTime.now().subtract(const Duration(days: 7)),
lastModified: DateTime.now().subtract(const Duration(days: 1)),
),
PlaylistModel(
id: '4',
name: 'Road Trip Classics',
description: 'Classic hits for long drives',
trackcount: 45,
isPublic: false,
createdDate: DateTime.now().subtract(const Duration(days: 90)),
lastModified: DateTime.now().subtract(const Duration(days: 10)),
),
];
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Playlists'),
elevation: 0,
backgroundColor: Theme.of(context).colorScheme.surface,
actions: [
IconButton(
onPressed: _showCreatePlaylistDialog,
icon: const Icon(Icons.add),
),
],
),
body: Column(
children: [
// Search Bar
Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: 'Search playlists...',
prefixIcon: const Icon(Icons.search),
suffixIcon: _searchController.text.isNotEmpty
? IconButton(
onPressed: () {
_searchController.clear();
_filterPlaylists('');
},
icon: const Icon(Icons.clear),
)
: null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
filled: true,
fillColor: Theme.of(context).colorScheme.surfaceContainerHighest,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
),
onChanged: (value) {
_filterPlaylists(value);
},
),
),
// Playlists Grid
Expanded(
child: _isLoading
? const Center(child: CircularProgressIndicator())
: _playlists.isEmpty
? _buildEmptyState()
: _buildPlaylistsGrid(),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: _showCreatePlaylistDialog,
child: const Icon(Icons.add),
),
);
}
Widget _buildPlaylistsGrid() {
return GridView.builder(
padding: const EdgeInsets.all(16.0),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.8,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
),
itemCount: _playlists.length,
itemBuilder: (context, index) {
final playlist = _playlists[index];
return Card(
child: InkWell(
onTap: () => _openPlaylist(playlist),
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Playlist Cover
Container(
width: double.infinity,
height: 120,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Theme.of(context).colorScheme.primary.withValues(alpha: 0.8),
Theme.of(context).colorScheme.secondary.withValues(alpha: 0.8),
],
),
borderRadius: BorderRadius.circular(8),
),
child: Stack(
children: [
// Playlist Icon
Positioned(
top: 8,
right: 8,
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.7),
borderRadius: BorderRadius.circular(4),
),
child: Icon(
Icons.playlist_play,
color: Colors.white,
size: 20,
),
),
),
// Public/Private Badge
if (playlist.isPublic)
Positioned(
bottom: 8,
left: 8,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(8),
),
child: Text(
'PUBLIC',
style: const TextStyle(
color: Colors.white,
fontSize: 8,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
),
const SizedBox(height: 12),
// Playlist Info
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
playlist.name,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
playlist.description,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 8),
Row(
children: [
Icon(
Icons.music_note,
size: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
const SizedBox(width: 4),
Text(
'${playlist.trackcount} tracks',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
const SizedBox(height: 4),
Text(
_formatDate(playlist.lastModified),
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
),
],
),
),
),
);
},
);
}
Widget _buildEmptyState() {
return Center(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.playlist_add,
size: 64,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
const SizedBox(height: 16),
Text(
'No playlists yet',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 8),
Text(
'Create your first playlist to get started',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: _showCreatePlaylistDialog,
icon: const Icon(Icons.add),
label: const Text('Create Playlist'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
),
),
],
),
),
);
}
void _filterPlaylists(String query) {
setState(() {
if (query.isEmpty) {
// Reset to all playlists
_loadPlaylists();
} else {
// Filter playlists (in real app, this would call API)
_playlists = _playlists.where((playlist) =>
playlist.name.toLowerCase().contains(query.toLowerCase())
).toList();
}
});
}
void _openPlaylist(PlaylistModel playlist) {
Navigator.pushNamed(context, '/playlist', arguments: playlist);
}
void _showCreatePlaylistDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Create Playlist'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: _playlistNameController,
decoration: const InputDecoration(
labelText: 'Playlist Name',
border: OutlineInputBorder(),
),
autofocus: true,
),
const SizedBox(height: 16),
TextField(
decoration: const InputDecoration(
labelText: 'Description (Optional)',
border: OutlineInputBorder(),
),
maxLines: 3,
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: _createPlaylist,
child: const Text('Create'),
),
],
),
);
}
void _createPlaylist() async {
final name = _playlistNameController.text.trim();
if (name.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Please enter a playlist name')),
);
return;
}
// Create playlist (in real app, this would call API)
final newPlaylist = PlaylistModel(
id: DateTime.now().millisecondsSinceEpoch.toString(),
name: name,
description: '',
trackcount: 0,
isPublic: false,
createdDate: DateTime.now(),
lastModified: DateTime.now(),
);
setState(() {
_playlists.insert(0, newPlaylist);
});
_playlistNameController.clear();
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Playlist "$name" created successfully')),
);
}
String _formatDate(DateTime date) {
final now = DateTime.now();
final difference = now.difference(date);
if (difference.inDays == 0) {
return 'Today';
} else if (difference.inDays == 1) {
return 'Yesterday';
} else if (difference.inDays < 7) {
return '${difference.inDays} days ago';
} else if (difference.inDays < 30) {
return '${(difference.inDays / 7).floor()} weeks ago';
} else {
return '${(difference.inDays / 30).floor()} months ago';
}
}
}