Files
SAVExSTATE/lib/login_screen.dart
T

328 lines
14 KiB
Dart

import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:url_launcher/url_launcher.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final _nameController = TextEditingController();
final _codeController = TextEditingController();
bool _isLoginMode = true;
bool _isUnlocked = false;
bool _showInfo = false;
bool _isLoading = false;
int _tierToGrant = 1;
static const Color terminalGreen = Color(0xFFE87D25);
// --- LOGIC: REQUEST LVL 1 (FREE) ---
Future<void> _requestFreeAccess() async {
final nameController = TextEditingController();
final contactController = TextEditingController();
bool isSending = false; // Internal state for the dialog
showDialog(
context: context,
builder: (context) => StatefulBuilder(
builder: (context, setDialogState) => AlertDialog(
backgroundColor: Colors.black,
shape: const RoundedRectangleBorder(side: BorderSide(color: terminalGreen)),
title: const Text("FREE_ACCESS_REQUEST", style: TextStyle(fontSize: 14)),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text("ENTER_INFO_FOR_KEY_DELIVERY:", style: TextStyle(fontSize: 10, color: Colors.white24)),
const SizedBox(height: 15),
TextField(controller: nameController, decoration: const InputDecoration(labelText: "ALIAS / NAME")),
const SizedBox(height: 10),
TextField(controller: contactController, decoration: const InputDecoration(labelText: "EMAIL_OR_CONTACT")),
],
),
actions: [
TextButton(
onPressed: isSending ? null : () => Navigator.pop(context),
child: const Text("[ CANCEL ]", style: TextStyle(color: Colors.white24))
),
isSending
? const Padding(
padding: EdgeInsets.only(right: 20),
child: CircularProgressIndicator(color: terminalGreen, strokeWidth: 2),
)
: ElevatedButton(
onPressed: () async {
if (contactController.text.trim().isEmpty) return;
setDialogState(() => isSending = true);
try {
await FirebaseFirestore.instance.collection('requests').add({
'name': nameController.text.trim(),
'contact': contactController.text.trim(),
'timestamp': FieldValue.serverTimestamp(),
'status': 'pending',
'requestedTier': 1,
'origin': 'FREE_REQUEST'
});
if (!mounted) return;
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("SIGNAL_SENT: SAGE_WILL_TRANSMIT_KEY"), backgroundColor: terminalGreen)
);
} catch (e) {
setDialogState(() => isSending = false);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("UPLINK_ERROR: $e"), backgroundColor: Colors.red)
);
}
}
},
child: const Text("[ SEND_REQUEST ]")
)
],
),
),
);
}
// --- LOGIC: VERIFY INVITE KEY ---
Future<void> _verifyInviteCode() async {
if (_codeController.text.isEmpty) return;
setState(() => _isLoading = true);
try {
final codeDoc = await FirebaseFirestore.instance.collection('invites').doc(_codeController.text.trim().toUpperCase()).get();
if (!mounted) return;
if (codeDoc.exists && codeDoc.data()?['used'] == false) {
setState(() {
_isUnlocked = true;
_tierToGrant = codeDoc.data()?['grantTier'] ?? 1;
});
} else {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("INVALID_OR_EXPIRED_KEY"), backgroundColor: Colors.red));
}
} catch (e) {
if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("SYNC_ERROR: $e")));
} finally {
if (mounted) setState(() => _isLoading = false);
}
}
// --- LOGIC: LOGIN / REGISTER ---
Future<void> _submit() async {
if (_emailController.text.isEmpty || _passwordController.text.isEmpty) return;
setState(() => _isLoading = true);
try {
if (_isLoginMode) {
await FirebaseAuth.instance.signInWithEmailAndPassword(email: _emailController.text.trim(), password: _passwordController.text.trim());
} else {
UserCredential userCredential = await FirebaseAuth.instance.createUserWithEmailAndPassword(email: _emailController.text.trim(), password: _passwordController.text.trim());
await FirebaseFirestore.instance.collection('invites').doc(_codeController.text.trim().toUpperCase()).update({'used': true, 'usedBy': userCredential.user!.uid});
await FirebaseFirestore.instance.collection('users').doc(userCredential.user!.uid).set({
'email': _emailController.text.trim(),
'displayName': _nameController.text.trim(),
'role': 'fan',
'tier': _tierToGrant,
'createdAt': FieldValue.serverTimestamp(),
});
}
} catch (e) {
if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString()), backgroundColor: Colors.red));
} finally {
if (mounted) setState(() => _isLoading = false);
}
}
void _showClaimDialog() {
final claimController = TextEditingController();
showDialog(
context: context,
builder: (context) => AlertDialog(
backgroundColor: Colors.black,
shape: const RoundedRectangleBorder(side: BorderSide(color: terminalGreen)),
title: const Text("CLAIM_ACCESS_KEY", style: TextStyle(fontSize: 14)),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text("ENTER_PAYPAL_EMAIL_OR_TRANS_ID:", style: TextStyle(fontSize: 10, color: Colors.white24)),
const SizedBox(height: 15),
TextField(controller: claimController, style: const TextStyle(color: Colors.white), decoration: const InputDecoration(hintText: "PAYPAL_RECEIPT_ID")),
],
),
actions: [
TextButton(onPressed: () => Navigator.pop(context), child: const Text("[ CANCEL ]")),
ElevatedButton(
onPressed: () async {
if (claimController.text.isEmpty) return;
await FirebaseFirestore.instance.collection('requests').add({
'id': claimController.text.trim(),
'timestamp': FieldValue.serverTimestamp(),
'status': 'pending',
'origin': 'PAYPAL_CLAIM'
});
if (!mounted) return;
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("SIGNAL_SENT: SAGE_WILL_VERIFY")));
},
child: const Text("[ SUBMIT ]")
)
],
)
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(40.0),
child: Column(
children: [
Image.asset('assets/images/logo.png', height: 100, color: terminalGreen),
const SizedBox(height: 30),
if (_showInfo) _buildTierInfo()
else if (_isLoginMode) _buildLoginFields()
else if (!_isUnlocked) _buildCodeVerifyFields()
else _buildRegistrationFields(),
const SizedBox(height: 30),
TextButton(
onPressed: () => setState(() => _showInfo = !_showInfo),
child: Text(_showInfo ? "< RETURN_TO_LOGIN" : "> VIEW_ACCESS_MATRIX",
style: const TextStyle(color: Colors.white24, fontSize: 10, letterSpacing: 2)),
),
],
),
),
),
);
}
Widget _buildTierInfo() {
return Column(
children: [
const Text("--- ACCESS_LEVEL_MATRIX ---", style: TextStyle(fontWeight: FontWeight.bold, letterSpacing: 2)),
const SizedBox(height: 25),
_tierRow("01_OBSERVER", "FREE", "30s Previews / Read-Only Comms"),
const SizedBox(height: 8),
SizedBox(
width: double.infinity,
child: OutlinedButton(
style: OutlinedButton.styleFrom(side: const BorderSide(color: terminalGreen), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero)),
onPressed: _requestFreeAccess,
child: const Text("[ REQUEST_LVL_01 ]", style: TextStyle(fontSize: 10, color: terminalGreen)),
),
),
const SizedBox(height: 20),
_tierRow("02_COLLECTOR", "\$25/MO", "Full Archive / Active Comms"),
_paypalBtn("ACQUIRE_LVL_02", "https://www.paypal.com/ncp/payment/YOUR_L2_LINK"),
const SizedBox(height: 20),
_tierRow("03_INVESTOR", "\$100/YR", "Extractions / Private Logs / Priority"),
_paypalBtn("ACQUIRE_LVL_03", "https://www.paypal.com/ncp/payment/YOUR_L3_LINK"),
const SizedBox(height: 30),
const Text("PAID_BUT_NO_KEY?", style: TextStyle(color: Colors.white24, fontSize: 9)),
TextButton(onPressed: _showClaimDialog, child: const Text("> LOG_PAYMENT_SIGNAL", style: TextStyle(color: terminalGreen, fontSize: 10))),
],
);
}
Widget _tierRow(String name, String price, String perks) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(name, style: const TextStyle(color: terminalGreen, fontWeight: FontWeight.bold, fontSize: 12)),
Text(price, style: const TextStyle(color: Colors.white, fontSize: 12)),
],
),
const SizedBox(height: 4),
Text(perks, style: const TextStyle(color: Colors.white38, fontSize: 9)),
],
);
}
Widget _paypalBtn(String label, String url) {
return SizedBox(
width: double.infinity,
child: OutlinedButton(
style: OutlinedButton.styleFrom(side: const BorderSide(color: terminalGreen), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero)),
onPressed: () async {
final Uri uri = Uri.parse(url);
if (await canLaunchUrl(uri)) await launchUrl(uri);
},
child: Text("[ $label ]", style: const TextStyle(fontSize: 10, color: terminalGreen)),
),
);
}
Widget _buildLoginFields() {
return Column(
children: [
const Text("[ SYSTEM_LOGIN_REQUIRED ]", style: TextStyle(letterSpacing: 2)),
const SizedBox(height: 25),
TextField(controller: _emailController, decoration: const InputDecoration(labelText: "USER_ID")),
const SizedBox(height: 10),
TextField(controller: _passwordController, obscureText: true, decoration: const InputDecoration(labelText: "ENCRYPT_PASS")),
const SizedBox(height: 30),
_isLoading ? const CircularProgressIndicator(color: terminalGreen) : ElevatedButton(onPressed: _submit, child: const Text("[ ENTER ]")),
TextButton(
onPressed: () => setState(() => _isLoginMode = false),
child: const Text("> INITIALIZE_NEW_KEY", style: TextStyle(color: Colors.white24, fontSize: 10))
),
],
);
}
Widget _buildCodeVerifyFields() {
return Column(
children: [
const Text("[ KEY_VALIDATION ]", style: TextStyle(letterSpacing: 2)),
const SizedBox(height: 25),
TextField(
controller: _codeController,
textAlign: TextAlign.center,
style: const TextStyle(letterSpacing: 8, color: Colors.white, fontWeight: FontWeight.bold),
decoration: const InputDecoration(hintText: "ENTER_KEY", hintStyle: TextStyle(color: Colors.white10))
),
const SizedBox(height: 30),
_isLoading ? const CircularProgressIndicator(color: terminalGreen) : ElevatedButton(onPressed: _verifyInviteCode, child: const Text("[ VALIDATE ]")),
TextButton(onPressed: () => setState(() => _isLoginMode = true), child: const Text("> BACK", style: TextStyle(color: Colors.white24, fontSize: 10))),
],
);
}
Widget _buildRegistrationFields() {
return Column(
children: [
Text("[ LVL_0${_tierToGrant}_ACCESS_GRANTED ]", style: const TextStyle(color: terminalGreen, letterSpacing: 2)),
const SizedBox(height: 25),
TextField(controller: _nameController, decoration: const InputDecoration(labelText: "ALIAS / NAME")),
const SizedBox(height: 10),
TextField(controller: _emailController, decoration: const InputDecoration(labelText: "EMAIL_ADDR")),
const SizedBox(height: 10),
TextField(controller: _passwordController, obscureText: true, decoration: const InputDecoration(labelText: "CREATE_PASS")),
const SizedBox(height: 30),
_isLoading ? const CircularProgressIndicator(color: terminalGreen) : ElevatedButton(onPressed: _submit, child: const Text("[ INITIALIZE_PROFILE ]")),
],
);
}
}