Initial Release: SAVEXSTATE Vault V1 - Cyber Orange Edition

This commit is contained in:
Zach Groth
2026-03-28 22:53:48 -05:00
commit d2863d2ce8
203 changed files with 8249 additions and 0 deletions
+136
View File
@@ -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)),
],
),
),
),
],
);
}
}