add support for token validation on login page
Some checks failed
Release / meta (push) Successful in 20s
Release / release-dev (push) Has been cancelled
Release / release-master (push) Has been cancelled
Release / android-build (push) Has been cancelled
Release / linux-build (push) Has been cancelled

This commit is contained in:
2025-12-14 12:15:39 +00:00
parent a2b38a7aec
commit 11a5a42ad4
4 changed files with 92 additions and 16 deletions

View File

@@ -1,10 +1,37 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:mileograph_flutter/services/authservice.dart';
import 'package:provider/provider.dart';
class LoginScreen extends StatelessWidget {
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
bool _checkingSession = true;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => _checkExistingSession());
}
Future<void> _checkExistingSession() async {
final auth = context.read<AuthService>();
try {
final valid = await auth.validateStoredToken();
if (!valid) return;
await auth.tryRestoreSession();
if (!mounted) return;
context.go('/');
} finally {
if (mounted) setState(() => _checkingSession = false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -24,7 +51,7 @@ class LoginScreen extends StatelessWidget {
color: Theme.of(context).textTheme.bodyLarge?.color,
),
),
TextSpan(
const TextSpan(
text: "O",
style: TextStyle(color: Colors.red),
),
@@ -35,7 +62,7 @@ class LoginScreen extends StatelessWidget {
),
),
],
style: TextStyle(
style: const TextStyle(
decoration: TextDecoration.none,
color: Colors.white,
fontFamily: "Tomatoes",
@@ -43,8 +70,17 @@ class LoginScreen extends StatelessWidget {
),
),
),
if (_checkingSession)
const Padding(
padding: EdgeInsets.only(top: 12),
child: SizedBox(
height: 24,
width: 24,
child: CircularProgressIndicator(strokeWidth: 2),
),
),
const SizedBox(height: 50),
LoginPanel(),
const LoginPanel(),
],
),
),

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'package:mileograph_flutter/components/calculator/calculator.dart';
import 'package:mileograph_flutter/components/pages/traction.dart';
@@ -444,10 +445,12 @@ class _NewEntryPageState extends State<NewEntryPage> {
});
_startController.text = data['start'] ?? '';
_endController.text = data['end'] ?? '';
_headcodeController.text = data['headcode'] ?? '';
_headcodeController.text =
data['headcode'] is String ? data['headcode'].toUpperCase() : '';
_notesController.text = data['notes'] ?? '';
_mileageController.text = data['mileage'] ?? '';
_networkController.text = data['network'] ?? '';
_networkController.text =
data['network'] is String ? data['network'].toUpperCase() : '';
} catch (_) {
// Ignore corrupt draft data
} finally {
@@ -575,6 +578,8 @@ class _NewEntryPageState extends State<NewEntryPage> {
),
TextFormField(
controller: _headcodeController,
textCapitalization: TextCapitalization.characters,
inputFormatters: const [_UpperCaseTextFormatter()],
decoration: const InputDecoration(
labelText: 'Headcode',
border: OutlineInputBorder(),
@@ -582,6 +587,8 @@ class _NewEntryPageState extends State<NewEntryPage> {
),
TextFormField(
controller: _networkController,
textCapitalization: TextCapitalization.characters,
inputFormatters: const [_UpperCaseTextFormatter()],
decoration: const InputDecoration(
labelText: 'Network',
border: OutlineInputBorder(),
@@ -817,6 +824,21 @@ class _NewEntryPageState extends State<NewEntryPage> {
}
}
class _UpperCaseTextFormatter extends TextInputFormatter {
const _UpperCaseTextFormatter();
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
return newValue.copyWith(
text: newValue.text.toUpperCase(),
selection: newValue.selection,
);
}
}
class _CalculatorPickerPage extends StatelessWidget {
const _CalculatorPickerPage({required this.onResult});
final ValueChanged<RouteResult> onResult;

View File

@@ -100,6 +100,24 @@ class AuthService extends ChangeNotifier {
}
}
Future<bool> validateStoredToken() async {
final token = await _tokenStorage.getToken();
if (token == null || token.isEmpty) return false;
try {
await api.get(
'/validate',
headers: {
'Authorization': 'Bearer $token',
'accept': 'application/json',
},
);
return true;
} catch (_) {
await _clearToken();
return false;
}
}
Future<void> _persistToken(String token) async {
await _tokenStorage.setToken(token);
}

View File

@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 0.1.2+1
version: 0.1.3+1
environment:
sdk: ^3.8.1