Initial Release: SAVEXSTATE Vault V1 - Cyber Orange Edition
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
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)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user