You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

497 lines
18 KiB

import 'package:flutter/material.dart';
import '../../data/session/session_service.dart';
import '../../data/firebase/firebase_service.dart';
/// Screen for user session management (login/logout).
class SessionScreen extends StatefulWidget {
final SessionService? sessionService;
final FirebaseService? firebaseService;
final VoidCallback? onSessionChanged;
const SessionScreen({
super.key,
this.sessionService,
this.firebaseService,
this.onSessionChanged,
});
@override
State<SessionScreen> createState() => _SessionScreenState();
}
class _SessionScreenState extends State<SessionScreen> {
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _userIdController = TextEditingController();
final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
final TextEditingController _nostrKeyController = TextEditingController();
bool _isLoading = false;
bool _useFirebaseAuth = false;
bool _useNostrLogin = false;
@override
void initState() {
super.initState();
// Check if Firebase Auth is available
_useFirebaseAuth = widget.firebaseService?.isEnabled == true &&
widget.firebaseService?.config.authEnabled == true;
}
@override
void dispose() {
_usernameController.dispose();
_userIdController.dispose();
_emailController.dispose();
_passwordController.dispose();
_nostrKeyController.dispose();
super.dispose();
}
Future<void> _handleLogin() async {
if (widget.sessionService == null) return;
setState(() {
_isLoading = true;
});
try {
// Handle Nostr login
if (_useNostrLogin) {
final nostrKey = _nostrKeyController.text.trim();
if (nostrKey.isEmpty) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please enter nsec or npub key'),
),
);
}
return;
}
// Validate format
if (!nostrKey.startsWith('nsec') && !nostrKey.startsWith('npub')) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Invalid key format. Expected nsec or npub.'),
backgroundColor: Colors.red,
),
);
}
return;
}
// Login with Nostr
await widget.sessionService!.loginWithNostr(nostrKey);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Nostr login successful'),
),
);
setState(() {});
widget.onSessionChanged?.call();
}
return;
}
// Handle Firebase or regular login
if (_useFirebaseAuth && widget.firebaseService != null) {
// Use Firebase Auth for authentication
final email = _emailController.text.trim();
final password = _passwordController.text.trim();
if (email.isEmpty || password.isEmpty) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please enter email and password'),
),
);
}
return;
}
// Authenticate with Firebase
final firebaseUser = await widget.firebaseService!.loginWithEmailPassword(
email: email,
password: password,
);
// Create session with Firebase user info
await widget.sessionService!.login(
id: firebaseUser.uid,
username: firebaseUser.email?.split('@').first ?? firebaseUser.uid,
token: await firebaseUser.getIdToken(),
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Login successful'),
),
);
setState(() {});
// Notify parent that session state changed
widget.onSessionChanged?.call();
}
} else {
// Simple validation mode (no Firebase Auth)
final username = _usernameController.text.trim();
final userId = _userIdController.text.trim();
if (username.isEmpty || userId.isEmpty) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please enter username and user ID'),
),
);
}
return;
}
// Basic validation: require minimum length
if (userId.length < 3) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('User ID must be at least 3 characters'),
),
);
}
return;
}
if (username.length < 2) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Username must be at least 2 characters'),
),
);
}
return;
}
// Create session (demo mode - no real authentication)
await widget.sessionService!.login(
id: userId,
username: username,
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Login successful (demo mode)'),
backgroundColor: Colors.orange,
),
);
setState(() {});
// Notify parent that session state changed
widget.onSessionChanged?.call();
}
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Login failed: ${e.toString().replaceAll('FirebaseException: ', '').replaceAll('SessionException: ', '')}'),
backgroundColor: Colors.red,
),
);
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
Future<void> _handleLogout() async {
if (widget.sessionService == null) return;
setState(() {
_isLoading = true;
});
try {
// Logout from session service first
await widget.sessionService!.logout();
// Also logout from Firebase Auth if enabled
if (_useFirebaseAuth && widget.firebaseService != null) {
try {
await widget.firebaseService!.logout();
} catch (e) {
// Log error but don't fail logout - session is already cleared
debugPrint('Warning: Firebase logout failed: $e');
}
}
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Logout successful'),
),
);
setState(() {});
// Notify parent that session state changed
widget.onSessionChanged?.call();
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Logout failed: ${e.toString().replaceAll('SessionException: ', '')}'),
backgroundColor: Colors.red,
),
);
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
@override
Widget build(BuildContext context) {
final isLoggedIn = widget.sessionService?.isLoggedIn ?? false;
final currentUser = widget.sessionService?.currentUser;
return Scaffold(
appBar: AppBar(
title: const Text('Session'),
),
body: _isLoading
? const Center(child: CircularProgressIndicator())
: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (isLoggedIn && currentUser != null) ...[
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Current Session',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
// Display Nostr profile if available
if (currentUser.nostrProfile != null) ...[
Row(
children: [
if (currentUser.nostrProfile!.picture != null)
CircleAvatar(
radius: 30,
backgroundImage: NetworkImage(
currentUser.nostrProfile!.picture!,
),
onBackgroundImageError: (_, __) {},
)
else
const CircleAvatar(
radius: 30,
child: Icon(Icons.person),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
currentUser.nostrProfile!.name ??
currentUser.nostrProfile!.displayName,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
if (currentUser.nostrProfile!.about != null)
Text(
currentUser.nostrProfile!.about!,
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
const SizedBox(height: 12),
const Divider(),
const SizedBox(height: 12),
],
Text('User ID: ${currentUser.id.substring(0, currentUser.id.length > 32 ? 32 : currentUser.id.length)}${currentUser.id.length > 32 ? '...' : ''}'),
Text('Username: ${currentUser.username}'),
Text(
'Created: ${DateTime.fromMillisecondsSinceEpoch(currentUser.createdAt).toString().split('.')[0]}',
),
],
),
),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _handleLogout,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
),
child: const Text('Logout'),
),
] else ...[
const Icon(
Icons.person_outline,
size: 64,
color: Colors.grey,
),
const SizedBox(height: 16),
const Text(
'Login',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
if (!_useFirebaseAuth) ...[
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.orange.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.orange.shade200),
),
child: Row(
children: [
Icon(Icons.info_outline, color: Colors.orange.shade700, size: 20),
const SizedBox(width: 8),
Expanded(
child: Text(
'Demo mode: No authentication required. Enter any valid user ID and username.',
style: TextStyle(
fontSize: 12,
color: Colors.orange.shade700,
),
),
),
],
),
),
],
const SizedBox(height: 24),
// Login method selector
SegmentedButton<bool>(
segments: const [
ButtonSegment<bool>(
value: false,
label: Text('Regular'),
),
ButtonSegment<bool>(
value: true,
label: Text('Nostr'),
),
],
selected: {_useNostrLogin},
onSelectionChanged: (Set<bool> newSelection) {
setState(() {
_useNostrLogin = newSelection.first;
});
},
),
const SizedBox(height: 24),
if (_useNostrLogin) ...[
TextField(
controller: _nostrKeyController,
decoration: const InputDecoration(
labelText: 'Nostr Key (nsec or npub)',
hintText: 'Enter your nsec or npub key',
border: OutlineInputBorder(),
helperText: 'Enter your Nostr private key (nsec) or public key (npub)',
),
maxLines: 3,
minLines: 1,
),
] else if (_useFirebaseAuth) ...[
TextField(
controller: _emailController,
decoration: const InputDecoration(
labelText: 'Email',
hintText: 'Enter your email',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.emailAddress,
autofillHints: const [AutofillHints.email],
),
const SizedBox(height: 16),
TextField(
controller: _passwordController,
decoration: const InputDecoration(
labelText: 'Password',
hintText: 'Enter your password',
border: OutlineInputBorder(),
),
obscureText: true,
autofillHints: const [AutofillHints.password],
),
] else ...[
TextField(
controller: _userIdController,
decoration: const InputDecoration(
labelText: 'User ID',
hintText: 'Enter user ID (min 3 characters)',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
TextField(
controller: _usernameController,
decoration: const InputDecoration(
labelText: 'Username',
hintText: 'Enter username (min 2 characters)',
border: OutlineInputBorder(),
),
),
],
const SizedBox(height: 24),
ElevatedButton(
onPressed: _isLoading ? null : _handleLogin,
child: _isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Login'),
),
],
],
),
),
);
}
}

Powered by TurnKey Linux.