import 'package:flutter/material.dart'; import 'package:jwt_decoder/jwt_decoder.dart'; import 'package:logging/logging.dart'; import 'package:sheetless/api.dart'; import 'package:sheetless/home_page.dart'; import 'package:sheetless/storage_helper.dart'; class LoginPage extends StatefulWidget { const LoginPage({super.key}); @override State createState() => _LoginPageState(); } class _LoginPageState extends State { final log = Logger("_LoginPageState"); final TextEditingController _urlController = TextEditingController( text: "https://sheetable.julian-mutter.de", ); final TextEditingController _emailController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); final _formKey = GlobalKey(); final StorageHelper _storageHelper = StorageHelper(); String? _error; bool loggingIn = false; @override void initState() { super.initState(); _restoreStoredValues(); } Future _restoreStoredValues() async { final jwt = await _storageHelper.readSecure(SecureStorageKey.jwt); if (jwt != null && await _isJwtValid(jwt)) { await _navigateToMainPage(); return; } final url = await _storageHelper.readSecure(SecureStorageKey.url); final email = await _storageHelper.readSecure(SecureStorageKey.email); if (url != null) { _urlController.text = url; } if (email != null) { _emailController.text = email; } } Future _isJwtValid(String jwt) async { try { bool expired = JwtDecoder.isExpired(jwt); return !expired; } on FormatException { return false; } } Future _login(String serverUrl, String email, String password) async { setState(() { _error = null; }); final apiClient = ApiClient(baseUrl: "$serverUrl/api"); final loginResult = await apiClient.login(email, password); if (loginResult.isOk()) { await _storageHelper.writeSecure(SecureStorageKey.url, serverUrl); await _storageHelper.writeSecure(SecureStorageKey.jwt, apiClient.token!); await _storageHelper.writeSecure(SecureStorageKey.email, email); await _navigateToMainPage(); } else { setState(() { _error = "Login failed.\n${loginResult.error()}"; }); } } Future _navigateToMainPage() async { final config = await _storageHelper.readConfig(); Navigator.of(context).pushReplacement( MaterialPageRoute(builder: (_) => MyHomePage(config: config)), ); } String? validateNotEmpty(String? content) { if (content == null || content.isEmpty) { return "Do not leave this field empty"; } return null; } void handleLoginPressed() async { if (loggingIn) return; loggingIn = true; if (_formKey.currentState!.validate()) { await _login( _urlController.text, _emailController.text, _passwordController.text, ); } loggingIn = false; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Login')), body: Padding( padding: const EdgeInsets.all(16.0), child: Form( key: _formKey, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ TextFormField( controller: _urlController, validator: validateNotEmpty, decoration: InputDecoration(labelText: 'Url'), textInputAction: TextInputAction.next, ), TextFormField( controller: _emailController, validator: validateNotEmpty, decoration: InputDecoration(labelText: 'Email'), textInputAction: TextInputAction.next, ), TextFormField( controller: _passwordController, validator: validateNotEmpty, // focusNode: _passwordFocusNode, decoration: InputDecoration(labelText: 'Password'), obscureText: true, textInputAction: TextInputAction .next, // with submit or go, onFieldSubmitted is not called onFieldSubmitted: (_) => handleLoginPressed(), ), // ), SizedBox(height: 5), ElevatedButton( onPressed: loggingIn ? null : handleLoginPressed, child: Text('Login'), ), if (_error != null) Padding( padding: const EdgeInsets.only(top: 8.0), child: Text(_error!, style: TextStyle(color: Colors.red)), ), ], ), ), ), ); } }