136 lines
5.4 KiB
Dart
136 lines
5.4 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:file_picker/file_picker.dart';
|
|
import 'package:firebase_storage/firebase_storage.dart';
|
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
import 'dart:typed_data';
|
|
|
|
class AdminMusicScreen extends StatefulWidget {
|
|
const AdminMusicScreen({super.key});
|
|
|
|
@override
|
|
State<AdminMusicScreen> createState() => _AdminMusicScreenState();
|
|
}
|
|
|
|
class _AdminMusicScreenState extends State<AdminMusicScreen> {
|
|
final _titleController = TextEditingController();
|
|
PlatformFile? _audioFile;
|
|
PlatformFile? _imageFile;
|
|
bool _isUploading = false;
|
|
double _uploadProgress = 0;
|
|
|
|
static const Color terminalGreen = Color(0xFF00FF00);
|
|
|
|
Future<void> _pickAudio() async {
|
|
final result = await FilePicker.platform.pickFiles(type: FileType.audio, withData: true);
|
|
if (result != null) setState(() => _audioFile = result.files.first);
|
|
}
|
|
|
|
Future<void> _pickImage() async {
|
|
final result = await FilePicker.platform.pickFiles(type: FileType.image, withData: true);
|
|
if (result != null) setState(() => _imageFile = result.files.first);
|
|
}
|
|
|
|
Future<void> _uploadTrack() async {
|
|
if (_audioFile == null || _imageFile == null || _titleController.text.isEmpty) {
|
|
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("SYSTEM_ERROR: DATA_REQUIRED")));
|
|
return;
|
|
}
|
|
|
|
setState(() { _isUploading = true; _uploadProgress = 0; });
|
|
|
|
try {
|
|
// 1. DYNAMIC CONTENT TYPE DETECTION
|
|
String audioContentType = 'audio/mpeg'; // Default MP3
|
|
if (_audioFile!.name.toLowerCase().endsWith('.wav')) audioContentType = 'audio/wav';
|
|
if (_audioFile!.name.toLowerCase().endsWith('.m4a')) audioContentType = 'audio/mp4';
|
|
|
|
// 2. UPLOAD AUDIO
|
|
final audioPath = 'music/${DateTime.now().millisecondsSinceEpoch}_${_audioFile!.name}';
|
|
final audioUploadTask = FirebaseStorage.instance.ref().child(audioPath).putData(
|
|
_audioFile!.bytes!,
|
|
SettableMetadata(contentType: audioContentType),
|
|
);
|
|
|
|
audioUploadTask.snapshotEvents.listen((event) {
|
|
setState(() => _uploadProgress = event.bytesTransferred / event.totalBytes);
|
|
});
|
|
|
|
final audioUrl = await (await audioUploadTask).ref.getDownloadURL();
|
|
|
|
// 3. UPLOAD IMAGE
|
|
final imagePath = 'covers/${DateTime.now().millisecondsSinceEpoch}_${_imageFile!.name}';
|
|
final imageUrl = await (await FirebaseStorage.instance.ref().child(imagePath).putData(
|
|
_imageFile!.bytes!,
|
|
SettableMetadata(contentType: 'image/jpeg'),
|
|
)).ref.getDownloadURL();
|
|
|
|
// 4. SAVE TO FIRESTORE
|
|
await FirebaseFirestore.instance.collection('tracks').add({
|
|
'title': _titleController.text.trim(),
|
|
'audioUrl': audioUrl,
|
|
'imageUrl': imageUrl,
|
|
'fileName': _audioFile!.name, // Storing this for the dynamic extension display
|
|
'uploadedAt': FieldValue.serverTimestamp(),
|
|
});
|
|
|
|
if (!mounted) return;
|
|
Navigator.pop(context);
|
|
} catch (e) {
|
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("EXECUTION_ERROR: $e")));
|
|
} finally {
|
|
setState(() => _isUploading = false);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: Colors.black,
|
|
appBar: AppBar(title: const Text("REGISTER_NEW_ASSET"), centerTitle: true),
|
|
body: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
children: [
|
|
TextField(controller: _titleController, decoration: const InputDecoration(labelText: "ASSET_TITLE")),
|
|
const SizedBox(height: 25),
|
|
_buildFileSlot("AUDIO_SOURCE", _audioFile?.name, Icons.audiotrack, _pickAudio),
|
|
const SizedBox(height: 15),
|
|
_buildFileSlot("VISUAL_ASSET", _imageFile?.name, Icons.image, _pickImage),
|
|
const SizedBox(height: 40),
|
|
if (_isUploading) ...[
|
|
LinearProgressIndicator(value: _uploadProgress, color: terminalGreen, backgroundColor: Colors.white10),
|
|
const SizedBox(height: 10),
|
|
Text("${(_uploadProgress * 100).toStringAsFixed(0)}%_SYNCING", style: const TextStyle(fontSize: 10)),
|
|
] else
|
|
SizedBox(width: double.infinity, child: ElevatedButton(onPressed: _uploadTrack, child: const Text("[ RELEASE_TRACK ]"))),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildFileSlot(String label, String? fileName, IconData icon, VoidCallback onTap) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(label, style: const TextStyle(color: Colors.white24, fontSize: 10)),
|
|
const SizedBox(height: 5),
|
|
InkWell(
|
|
onTap: onTap,
|
|
child: Container(
|
|
padding: const EdgeInsets.all(15),
|
|
decoration: BoxDecoration(border: Border.all(color: fileName == null ? Colors.white10 : terminalGreen)),
|
|
child: Row(
|
|
children: [
|
|
Icon(icon, color: fileName == null ? Colors.grey : terminalGreen, size: 20),
|
|
const SizedBox(width: 15),
|
|
Expanded(child: Text(fileName ?? "SELECT_FILE...", style: TextStyle(color: fileName == null ? Colors.grey : Colors.white, fontSize: 14), overflow: TextOverflow.ellipsis)),
|
|
const Text("[ BROWSE ]", style: TextStyle(color: terminalGreen, fontSize: 10)),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
} |