Initial Release: SAVEXSTATE Vault V1 - Cyber Orange Edition
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'player_service.dart'; // REQUIRED
|
||||
|
||||
class MusicVaultView extends StatefulWidget {
|
||||
const MusicVaultView({super.key});
|
||||
@override
|
||||
State<MusicVaultView> createState() => _MusicVaultViewState();
|
||||
}
|
||||
|
||||
class _MusicVaultViewState extends State<MusicVaultView> {
|
||||
static const Color terminalGreen = Color(0xFFE87D25);
|
||||
int _userTier = 1;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fetchUserTier();
|
||||
}
|
||||
|
||||
Future<void> _fetchUserTier() async {
|
||||
final user = FirebaseAuth.instance.currentUser;
|
||||
if (user != null) {
|
||||
final doc = await FirebaseFirestore.instance.collection('users').doc(user.uid).get();
|
||||
if (mounted) setState(() => _userTier = doc.data()?['tier'] ?? 1);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StreamBuilder<QuerySnapshot>(
|
||||
stream: FirebaseFirestore.instance.collection('tracks').orderBy('uploadedAt', descending: true).snapshots(),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) return const Center(child: CircularProgressIndicator(color: terminalGreen));
|
||||
final tracks = snapshot.data!.docs;
|
||||
|
||||
if (tracks.isEmpty) return const Center(child: Text("ARCHIVE_NULL / AWAITING_DROP"));
|
||||
|
||||
return ListView.builder(
|
||||
itemCount: tracks.length,
|
||||
padding: const EdgeInsets.only(top: 10, bottom: 20),
|
||||
itemBuilder: (context, index) {
|
||||
final t = tracks[index].data() as Map<String, dynamic>;
|
||||
final String docId = tracks[index].id;
|
||||
|
||||
// Check if this specific track is the one playing
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: PlayerService().currentTrack,
|
||||
builder: (context, activeTrack, child) {
|
||||
final bool isCurrent = activeTrack != null && activeTrack['audioUrl'] == t['audioUrl'];
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: isCurrent ? terminalGreen.withValues(alpha: 0.05) : Colors.transparent,
|
||||
border: Border(left: BorderSide(color: isCurrent ? terminalGreen : Colors.white12, width: 2)),
|
||||
),
|
||||
child: ListTile(
|
||||
leading: Container(
|
||||
width: 45, height: 45,
|
||||
decoration: BoxDecoration(border: Border.all(color: isCurrent ? terminalGreen : Colors.white10)),
|
||||
child: Image.network(
|
||||
"${t['imageUrl']}${t['imageUrl'].contains('?') ? '&' : '?'}t=${DateTime.now().millisecondsSinceEpoch}",
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (context, e, s) => const Icon(Icons.audiotrack, color: terminalGreen, size: 20),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
"${t['title'].toString().toUpperCase()}.${(t['fileName'] ?? 'MP3').split('.').last.toUpperCase()}",
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: isCurrent ? terminalGreen : Colors.white),
|
||||
),
|
||||
subtitle: Text(isCurrent ? "SYSTEM_STATUS: RUNNING" : "SYSTEM_STATUS: ENCRYPTED", style: const TextStyle(fontSize: 9, color: Colors.white24)),
|
||||
trailing: Icon(isCurrent ? Icons.graphic_eq : Icons.play_arrow_outlined, color: terminalGreen, size: 18),
|
||||
onTap: () => PlayerService().play(t, _userTier),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user