From 882bf550097239aa04697e0ab6b2fd9e1ad1daad Mon Sep 17 00:00:00 2001 From: Benimautner Date: Fri, 12 May 2023 13:53:41 +0200 Subject: [PATCH] added server recommendations made listId optional --- lib/api/list_implementation.dart | 6 +- lib/global.dart | 13 +- lib/models/task.dart | 2 +- lib/pages/user/login.dart | 247 +++++++++++++++++++++---------- lib/service/services.dart | 27 +++- 5 files changed, 203 insertions(+), 92 deletions(-) diff --git a/lib/api/list_implementation.dart b/lib/api/list_implementation.dart index 8881529..1debbbf 100644 --- a/lib/api/list_implementation.dart +++ b/lib/api/list_implementation.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:developer'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:vikunja_app/api/client.dart'; @@ -48,9 +47,10 @@ class ListAPIService extends APIService implements ListService { Future?> getAll() { return client.get('/lists').then( (list) { - if (list == null) return null; + if (list == null || list.statusCode != 200) return null; if (list.body.toString().isEmpty) return Future.value([]); + print(list.statusCode); return convertList(list.body, (result) => TaskList.fromJson(result));}); } @@ -66,7 +66,7 @@ class ListAPIService extends APIService implements ListService { } return client.get('/namespaces/$namespaceId/lists').then( (list) { - if (list == null) return null; + if (list == null || list.statusCode != 200) return null; return convertList(list.body, (result) => TaskList.fromJson(result)); }); } diff --git a/lib/global.dart b/lib/global.dart index cbd67c7..e66872c 100644 --- a/lib/global.dart +++ b/lib/global.dart @@ -1,7 +1,6 @@ import 'dart:developer' as dev; import 'package:flutter/material.dart'; -import 'package:flutter_timezone/flutter_timezone.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:vikunja_app/api/bucket_implementation.dart'; import 'package:vikunja_app/api/client.dart'; @@ -19,7 +18,6 @@ import 'package:vikunja_app/managers/user.dart'; import 'package:vikunja_app/models/user.dart'; import 'package:vikunja_app/service/services.dart'; import 'package:timezone/data/latest_all.dart' as tz; -import 'package:flutter_local_notifications/flutter_local_notifications.dart'as notifs; import 'package:workmanager/workmanager.dart'; @@ -147,18 +145,21 @@ class VikunjaGlobalState extends State { } - void logoutUser(BuildContext context) { - _storage.deleteAll().then((_) { + void logoutUser(BuildContext context) async { +// _storage.deleteAll().then((_) { + var userId = await _storage.read(key: "currentUser"); + _storage.delete(key: userId!); //delete token + _storage.delete(key: "${userId}_base"); Navigator.pop(context); setState(() { client.reset(); _currentUser = null; }); - }).catchError((err) { + /* }).catchError((err) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text('An error occured while logging out!'), )); - }); + });*/ } void _loadCurrentUser() async { diff --git a/lib/models/task.dart b/lib/models/task.dart index a97de1f..e5c0773 100644 --- a/lib/models/task.dart +++ b/lib/models/task.dart @@ -10,7 +10,7 @@ import 'package:vikunja_app/utils/checkboxes_in_text.dart'; class Task { final int id; final int? parentTaskId, priority, bucketId; - final int listId; + final int? listId; final DateTime created, updated; DateTime? dueDate, startDate, endDate; final List reminderDates; diff --git a/lib/pages/user/login.dart b/lib/pages/user/login.dart index 7650513..507d232 100644 --- a/lib/pages/user/login.dart +++ b/lib/pages/user/login.dart @@ -2,6 +2,7 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:vikunja_app/api/client.dart'; import 'package:vikunja_app/global.dart'; import 'package:vikunja_app/models/user.dart'; @@ -23,36 +24,43 @@ class _LoginPageState extends State { final _formKey = GlobalKey(); bool _loading = false; bool _rememberMe = false; + bool init = false; + List pastServers = []; final _serverController = TextEditingController(); final _usernameController = TextEditingController(); final _passwordController = TextEditingController(); + final _serverSuggestionController = SuggestionsBoxController(); + + @override void initState() { super.initState(); - Future.delayed(Duration.zero, () { - if(VikunjaGlobal.of(context).expired) { - ScaffoldMessenger.of(context) - .showSnackBar( - SnackBar( - content: Text( - "Login has expired. Please reenter your details!"))); + Future.delayed(Duration.zero, () async{ + if (VikunjaGlobal.of(context).expired) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text("Login has expired. Please reenter your details!"))); setState(() { _serverController.text = VikunjaGlobal.of(context).client.base; - _usernameController.text = VikunjaGlobal.of(context).currentUser?.username ?? ""; + _usernameController.text = + VikunjaGlobal.of(context).currentUser?.username ?? ""; }); } final client = VikunjaGlobal.of(context).client; - VikunjaGlobal.of(context).settingsManager.getIgnoreCertificates().then((value) => setState(() => client.ignoreCertificates = value == "1")); + await VikunjaGlobal.of(context).settingsManager.getIgnoreCertificates().then( + (value) => setState(() => client.ignoreCertificates = value == "1")); + + await VikunjaGlobal.of(context).settingsManager.getPastServers().then((value) { + print(value); + if (value != null) setState(() => pastServers = value); + }); }); } - - @override Widget build(BuildContext ctx) { - Client client = VikunjaGlobal.of(context).client; + Client client = VikunjaGlobal.of(context).client; return Scaffold( body: Center( @@ -79,18 +87,78 @@ class _LoginPageState extends State { ), Padding( padding: vStandardVerticalPadding, - child: TextFormField( - enabled: !_loading, - controller: _serverController, - autocorrect: false, - autofillHints: [AutofillHints.url], - validator: (address) { - return (isUrl(address) || address != null || address!.isEmpty) ? null : 'Invalid URL'; - }, - decoration: new InputDecoration( - border: OutlineInputBorder(), - labelText: 'Server Address'), - ), + child: Row(children: [ + Expanded( + child: TypeAheadFormField( + suggestionsBoxController: _serverSuggestionController, + getImmediateSuggestions: true, + enabled: !_loading, + validator: (address) { + return (isUrl(address) || + address != null || + address!.isEmpty) + ? null + : 'Invalid URL'; + }, + textFieldConfiguration: TextFieldConfiguration( + controller: _serverController, + decoration: new InputDecoration( + border: OutlineInputBorder(), + labelText: 'Server Address'), + ), + onSuggestionSelected: (suggestion) { + _serverController.text = suggestion; + }, + itemBuilder: (BuildContext context, Object? itemData) { + return Card( + + child: Container( + + padding: EdgeInsets.all(10), + child: + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(itemData.toString()), + IconButton(onPressed: () { + setState(() { + pastServers.remove(itemData.toString()); + _serverSuggestionController.suggestionsBox?.close(); + VikunjaGlobal.of(context).settingsManager.setPastServers(pastServers); + + }); + }, icon: Icon(Icons.clear)) + ], + )) + ); + }, + suggestionsCallback: (String pattern) { + List matches = []; + matches.addAll(pastServers); + matches.retainWhere((s){ + return s.toLowerCase().contains(pattern.toLowerCase()); + }); + return matches; + }, + ), + ), + /* + DropdownButton( + onChanged: (String? value) { + // This is called when the user selects an item. + setState(() { + if (value != null) _serverController.text = value; + }); + }, + items: pastServers + .map>((dynamic value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + ),*/ + ]), ), Padding( padding: vStandardVerticalPadding, @@ -119,7 +187,8 @@ class _LoginPageState extends State { padding: vStandardVerticalPadding, child: CheckboxListTile( value: _rememberMe, - onChanged: (value) => setState( () => _rememberMe = value ?? false), + onChanged: (value) => + setState(() => _rememberMe = value ?? false), title: Text("Remember me"), ), ), @@ -145,25 +214,42 @@ class _LoginPageState extends State { builder: (context) => RegisterPage())), child: VikunjaButtonText('Register'), )), - Builder(builder: (context) => FancyButton( - onPressed: () { - if(_formKey.currentState!.validate() && _serverController.text.isNotEmpty) { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => - LoginWithWebView(_serverController.text))).then((btp) { if(btp != null) _loginUserByClientToken(btp);}); - } else { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Please enter your frontend url"))); - } - }, - child: VikunjaButtonText("Login with Frontend"))), - client.ignoreCertificates != null ? - CheckboxListTile(title: Text("Ignore Certificates"), value: client.ignoreCertificates, onChanged: (value) { - setState(() => client.reload_ignore_certs(value ?? false)); - VikunjaGlobal.of(context).settingsManager.setIgnoreCertificates(value ?? false); - VikunjaGlobal.of(context).client.ignoreCertificates = value ?? false; - }) : ListTile(title: Text("...")) - ], + Builder( + builder: (context) => FancyButton( + onPressed: () { + if (_formKey.currentState!.validate() && + _serverController.text.isNotEmpty) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => LoginWithWebView( + _serverController.text))).then( + (btp) { + if (btp != null) _loginUserByClientToken(btp); + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + "Please enter your frontend url"))); + } + }, + child: VikunjaButtonText("Login with Frontend"))), + CheckboxListTile( + title: Text("Ignore Certificates"), + value: client.ignoreCertificates, + onChanged: (value) { + setState(() => + client.reload_ignore_certs(value ?? false)); + VikunjaGlobal.of(context) + .settingsManager + .setIgnoreCertificates(value ?? false); + VikunjaGlobal.of(context) + .client + .ignoreCertificates = value ?? false; + }) + + ], ), ), ), @@ -177,52 +263,53 @@ class _LoginPageState extends State { String _server = _serverController.text; String _username = _usernameController.text; String _password = _passwordController.text; - if(_server.isEmpty) - return; + if (_server.isEmpty) return; + + if(!pastServers.contains(_server)) pastServers.add(_server); + await VikunjaGlobal.of(context).settingsManager.setPastServers(pastServers); + setState(() => _loading = true); try { var vGlobal = VikunjaGlobal.of(context); vGlobal.client.configure(base: _server); Server? info = await vGlobal.serverService.getInfo(); - if(info == null) - throw Exception("Getting server info failed"); + if (info == null) throw Exception("Getting server info failed"); UserTokenPair newUser; - newUser = - await vGlobal.newUserService!.login( - _username, _password, rememberMe: this._rememberMe); + newUser = await vGlobal.newUserService! + .login(_username, _password, rememberMe: this._rememberMe); if (newUser.error == 1017) { TextEditingController totpController = TextEditingController(); - await showDialog(context: context, builder: (context) => - new AlertDialog( - title: Text("Enter One Time Passcode"), - content: TextField( - controller: totpController, keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), child: Text("Login")) - ], - )); - newUser = - await vGlobal.newUserService!.login( - _username, _password, rememberMe: this._rememberMe, - totp: totpController.text); - } else if(newUser.error > 0) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(newUser.errorString))); + await showDialog( + context: context, + builder: (context) => new AlertDialog( + title: Text("Enter One Time Passcode"), + content: TextField( + controller: totpController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text("Login")) + ], + )); + newUser = await vGlobal.newUserService!.login(_username, _password, + rememberMe: this._rememberMe, totp: totpController.text); + } else if (newUser.error > 0) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text(newUser.errorString))); } if (newUser.error == 0) - vGlobal.changeUser( - newUser.user!, token: newUser.token, base: _server); - + vGlobal.changeUser(newUser.user!, token: newUser.token, base: _server); } catch (ex) { - /* log(stacktrace.toString()); + /* log(stacktrace.toString()); showDialog( context: context, builder: (context) => new AlertDialog( @@ -246,12 +333,16 @@ class _LoginPageState extends State { _loginUserByClientToken(BaseTokenPair baseTokenPair) async { VikunjaGlobalState vGS = VikunjaGlobal.of(context); - vGS.client.configure(token: baseTokenPair.token, base: baseTokenPair.base, authenticated: true); + vGS.client.configure( + token: baseTokenPair.token, + base: baseTokenPair.base, + authenticated: true); setState(() => _loading = true); try { var newUser = await vGS.newUserService?.getCurrentUser(); - if(newUser != null) - vGS.changeUser(newUser, token: baseTokenPair.token, base: baseTokenPair.base); + if (newUser != null) + vGS.changeUser(newUser, + token: baseTokenPair.token, base: baseTokenPair.base); } catch (e) { log(e.toString()); } diff --git a/lib/service/services.dart b/lib/service/services.dart index 9f45908..d7a44ff 100644 --- a/lib/service/services.dart +++ b/lib/service/services.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:vikunja_app/api/response.dart'; @@ -232,17 +233,27 @@ class SettingsManager { Map defaults = { "ignore-certificates": "0", "get-version-notifications": "1", - "workmanager-duration": "0" + "workmanager-duration": "0", + "recent-servers": "[\"https://try.vikunja.io\"]", }; - SettingsManager(this._storage) { + void applydefaults() { defaults.forEach((key, value) { - _storage.containsKey(key: key).then((is_created) { - if (!is_created) _storage.write(key: key, value: value); + _storage.containsKey(key: key).then((is_created) async { + if (!is_created) { + print("iscreated $is_created"); + print("default not set, writing $value to $key"); + await _storage.write(key: key, value: value); + } }); }); } + + SettingsManager(this._storage) { + applydefaults(); + } + Future getIgnoreCertificates() { return _storage.read(key: "ignore-certificates"); } @@ -266,6 +277,14 @@ class SettingsManager { return _storage.write(key: "workmanager-duration", value: duration.inMinutes.toString()); } + Future?> getPastServers() { + return _storage.read(key: "recent-servers").then((value) => (jsonDecode(value!) as List).cast()); + } + Future setPastServers(List? server) { + var val = jsonEncode(server); + print("val: $val"); + return _storage.write(key: "recent-servers", value: jsonEncode(server)); + } }