diff --git a/flutter/lib/api/api_client.dart b/flutter/lib/api/api_client.dart index 6358197..d0dc986 100644 --- a/flutter/lib/api/api_client.dart +++ b/flutter/lib/api/api_client.dart @@ -162,6 +162,20 @@ class APIClient { ); } + static Future updateUser(TokenSource auth, String uid, {String? username, String? proToken}) async { + return await _request( + name: 'updateUser', + method: 'PATCH', + relURL: 'users/$uid', + jsonBody: { + if (username != null) 'username': username, + if (proToken != null) 'pro_token': proToken, + }, + fn: User.fromJson, + authToken: auth.getToken(), + ); + } + static Future addClient(TokenSource auth, String fcmToken, String agentModel, String agentVersion, String? name, String clientType) async { return await _request( name: 'addClient', diff --git a/flutter/lib/pages/account/account.dart b/flutter/lib/pages/account/account.dart index 1df9412..cce61b2 100644 --- a/flutter/lib/pages/account/account.dart +++ b/flutter/lib/pages/account/account.dart @@ -13,6 +13,7 @@ import 'package:simplecloudnotifier/state/application_log.dart'; import 'package:simplecloudnotifier/state/globals.dart'; import 'package:simplecloudnotifier/state/app_auth.dart'; import 'package:simplecloudnotifier/types/immediate_future.dart'; +import 'package:simplecloudnotifier/utils/dialogs.dart'; import 'package:simplecloudnotifier/utils/navi.dart'; import 'package:simplecloudnotifier/utils/toaster.dart'; import 'package:simplecloudnotifier/utils/ui.dart'; @@ -359,13 +360,15 @@ class _AccountRootPageState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ UI.buttonIconOnly( - onPressed: () {/*TODO*/}, + onPressed: _changeUsername, icon: FontAwesomeIcons.pen, ), const SizedBox(height: 4), if (!user.isPro) UI.buttonIconOnly( - onPressed: () {/*TODO*/}, + onPressed: () { + Toaster.info("Not Implemented", "Account Upgrading will be implemented in a later version"); // TODO + }, icon: FontAwesomeIcons.cartCircleArrowUp, ), ], @@ -502,4 +505,31 @@ class _AccountRootPageState extends State { void _deleteAccount() async { //TODO } + + void _changeUsername() async { + final acc = AppAuth(); + if (!acc.isAuth()) return; + + var newusername = await UIDialogs.showTextInput(context, 'Change your public username', 'Enter new username'); + if (newusername == null) return; + + newusername = newusername.trim(); + if (newusername == '') { + Toaster.error("Error", 'Username cannot be empty'); + return; + } + + try { + final user = await APIClient.updateUser(acc, acc.userID!, username: newusername); + setState(() { + futureUser = ImmediateFuture.ofValue(user); + }); + Toaster.success("Success", 'Username changed'); + + _backgroundRefresh(); + } catch (exc, trace) { + Toaster.error("Error", 'Failed to update username'); + ApplicationLog.error('Failed to update username: ' + exc.toString(), trace: trace); + } + } } diff --git a/flutter/lib/utils/dialogs.dart b/flutter/lib/utils/dialogs.dart new file mode 100644 index 0000000..6634909 --- /dev/null +++ b/flutter/lib/utils/dialogs.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +class UIDialogs { + static Future showTextInput(BuildContext context, String title, String hintText) { + var _textFieldController = TextEditingController(); + + return showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(title), + content: TextField( + autofocus: true, + controller: _textFieldController, + decoration: InputDecoration(hintText: hintText), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text('Cancel'), + ), + TextButton( + onPressed: () => Navigator.of(context).pop(_textFieldController.text), + child: Text('OK'), + ), + ], + ), + ); + } +}