Multiple improvements in authentication functionality

This commit is contained in:
Shivering Ocelot committed 2025-05-21 22:27:35 -05:00
1 parent bd5c4e002f
commit fbee31841d
11 files changed
+260 -17

No files matched your search

+19
View File
@@ -1,5 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'chat_list_screen.dart';
import 'user_checker.dart';
import 'backend.dart';
class AppMenu extends StatelessWidget {
const AppMenu({super.key});
@@ -21,6 +24,22 @@ class AppMenu extends StatelessWidget {
);
},
),
ListTile(
title: const Text('Logout'),
onTap: () async {
final storage = const FlutterSecureStorage();
await storage.delete(key: 'username');
await storage.delete(key: 'password');
Backend.disconnect();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UserChecker(),
),
);
},
),
// Add other settings options here
],
),
+28 -3
View File
@@ -6,26 +6,46 @@ import 'dart:async'; // Import for handling Completers
class Backend {
static WebSocket? _socket;
static String? _url;
static String? _username;
static Map<int, Completer> _pendingActions = {};
static Map<int, StreamController<Map<String, dynamic>>> _chatControllers = {};
static int _nextNonce = 0; // Tracking nonce for each action
static StreamController<Map<String, dynamic>> _packetStreamController = StreamController.broadcast();
// Authentication
static ({String username, String password})? _credentials;
static Completer? _loginCompleter;
static Completer? _credentialsCompleter;
static void setup(String url) {
_url = url;
connect();
}
static void auth(String username, String password) {
static Future<void> auth(String username, String password) {
_socket!.send(json.encode({'username': username, 'password': password}));
_username = username;
_credentials = (username: username, password: password);
if (_credentialsCompleter != null) {
_credentialsCompleter!.complete(_credentials);
_credentialsCompleter = null;
}
_loginCompleter = Completer();
return _loginCompleter!.future;
}
static void connect() {
_socket = WebSocket(_url!);
_socket!.onOpen.listen((event) {
print('Connected to the WebSocket server');
print('Logging in...');
if (_credentials != null) {
_socket!.send(json.encode({'username': _credentials!.username, 'password': _credentials!.password}));
} else {
_credentialsCompleter = Completer();
_credentialsCompleter!.future.then((credentials) {
_socket!.send(json.encode({'username': credentials.username, 'password': credentials.password}));
});
}
_loginCompleter?.complete();
});
_socket!.onError.listen((event) {
print('Error: $event');
@@ -40,6 +60,11 @@ class Backend {
});
}
static void disconnect() {
_socket?.close();
_loginCompleter = null;
}
static void _reconnect() {
Future.delayed(Duration(seconds: 5), () {
connect();
+23 -5
View File
@@ -8,6 +8,13 @@ class LoginScreen extends StatelessWidget {
final TextEditingController usernameController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
final FocusNode usernameFocusNode = FocusNode();
final FocusNode passwordFocusNode = FocusNode();
void _handleLogin() {
onLogin(usernameController.text, passwordController.text);
}
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -18,18 +25,29 @@ class LoginScreen extends StatelessWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(controller: usernameController, decoration: InputDecoration(labelText: 'Username')),
TextField(
controller: usernameController,
focusNode: usernameFocusNode,
decoration: InputDecoration(labelText: 'Username'),
textInputAction: TextInputAction.next,
onSubmitted: (_) {
FocusScope.of(context).requestFocus(passwordFocusNode);
},
),
SizedBox(height: 20),
TextField(
controller: passwordController,
focusNode: passwordFocusNode,
decoration: InputDecoration(labelText: 'Password'),
obscureText: true, // Hide password characters
obscureText: true,
textInputAction: TextInputAction.done,
onSubmitted: (_) {
_handleLogin();
},
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
onLogin(usernameController.text, passwordController.text);
},
onPressed: _handleLogin,
child: Text('Login'),
),
],
+24 -8
View File
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'backend.dart';
import 'login_screen.dart';
import 'chat_screen.dart';
@@ -11,12 +12,26 @@ class UserChecker extends StatefulWidget {
class _UserCheckerState extends State<UserChecker> {
String? username;
bool isLoading = false;
bool isLoading = true;
final _storage = const FlutterSecureStorage();
@override
void initState() {
super.initState();
username = null;
_checkSavedCredentials();
}
Future<void> _checkSavedCredentials() async {
final savedUsername = await _storage.read(key: 'username');
final savedPassword = await _storage.read(key: 'password');
if (savedUsername != null && savedPassword != null) {
_handleLogin(savedUsername, savedPassword);
} else {
setState(() {
isLoading = false;
});
}
}
static void fetchChats(Function(List<int>) onSuccess) {
@@ -28,13 +43,15 @@ class _UserCheckerState extends State<UserChecker> {
});
}
void _handleLogin(String user, String password) {
Backend.auth(user, password);
void _handleLogin(String user, String password) async {
setState(() {
username = user;
isLoading = true; // Show loading spinner
isLoading = true;
});
await Backend.auth(user, password);
await _storage.write(key: 'username', value: user);
await _storage.write(key: 'password', value: password);
fetchChats((chatIds) {
if (chatIds.isEmpty) {
@@ -49,14 +66,13 @@ class _UserCheckerState extends State<UserChecker> {
);
}
});
}
@override
Widget build(BuildContext context) {
print("Build UserChecker: $username $isLoading");
if (isLoading) {
return Center(child: CircularProgressIndicator()); // Loading state UI
return Center(child: CircularProgressIndicator());
}
return LoginScreen(onLogin: _handleLogin);