-
Notifications
You must be signed in to change notification settings - Fork 354
Add username unavailability indicator and validation to Edit Profile … #580
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 16 commits
06db5ac
847dd64
8e79723
be91e5a
e9851b9
4602600
0a0ec5f
1c427f9
ec564d6
d024bd3
9c49bb7
a8926cd
1f65dea
0bf8294
9a70110
31665f5
cbc7a09
55aee19
86ef78d
5f08373
683c6c3
1ea8357
29392b1
f44d70c
844fc6d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -27,7 +27,7 @@ class EditProfileController extends GetxController { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| RxBool isLoading = false.obs; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Rx<bool> usernameAvailable = false.obs; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Rx<bool> usernameChecking = false.obs; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bool removeImage = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bool showSuccessSnackbar = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -259,49 +259,30 @@ class EditProfileController extends GetxController { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Update USERNAME | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isUsernameChanged()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var usernameAvail = await isUsernameAvailable( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| usernameController.text.trim(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!usernameAvail) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| usernameAvailable.value = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| customSnackbar( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AppLocalizations.of(Get.context!)!.usernameUnavailable, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AppLocalizations.of(Get.context!)!.usernameInvalidOrTaken, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LogType.error, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SemanticsService.announce( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AppLocalizations.of(Get.context!)!.usernameInvalidOrTaken, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TextDirection.ltr, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Create new doc of New Username | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await tables.createRow( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| databaseId: userDatabaseID, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tableId: usernameTableID, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rowId: usernameController.text.trim(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data: {'email': authStateController.email}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Delete Old Username doc, so Username can be re-usable | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await tables.createRow( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| databaseId: userDatabaseID, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tableId: usernameTableID, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rowId: usernameController.text.trim(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data: {'email': authStateController.email}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await tables.deleteRow( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| databaseId: userDatabaseID, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tableId: usernameTableID, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rowId: oldUsername, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await tables.updateRow( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| databaseId: userDatabaseID, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tableId: usersTableID, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rowId: authStateController.uid!, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data: {"username": usernameController.text.trim()}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log(e.toString()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
270
to
294
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Silent error handling may mask username update failures. The Consider re-throwing or setting Suggested improvement try {
await tables.createRow(
databaseId: userDatabaseID,
tableId: usernameTableID,
rowId: usernameController.text.trim(),
data: {'email': authStateController.email},
);
await tables.deleteRow(
databaseId: userDatabaseID,
tableId: usernameTableID,
rowId: oldUsername,
);
await tables.updateRow(
databaseId: userDatabaseID,
tableId: usersTableID,
rowId: authStateController.uid!,
data: {"username": usernameController.text.trim()},
);
} catch (e) {
log(e.toString());
+ showSuccessSnackbar = false;
+ rethrow; // Let the outer catch handle user notification
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await tables.updateRow( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| databaseId: userDatabaseID, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tableId: usersTableID, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rowId: authStateController.uid!, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data: {"username": usernameController.text.trim()}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //Update user DISPLAY-NAME | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add translations for these strings for your native languages |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,13 @@ | ||
| import 'dart:io'; | ||
|
|
||
| import 'package:flutter/material.dart'; | ||
| import 'package:get/get.dart'; | ||
| import 'package:loading_animation_widget/loading_animation_widget.dart'; | ||
| import 'package:resonate/themes/theme_controller.dart'; | ||
| import 'package:resonate/utils/debouncer.dart'; | ||
| import 'package:resonate/utils/enums/log_type.dart'; | ||
| import 'package:resonate/utils/ui_sizes.dart'; | ||
| import 'package:resonate/views/widgets/loading_dialog.dart'; | ||
| import 'package:resonate/views/widgets/snackbar.dart'; | ||
| import 'package:resonate/l10n/app_localizations.dart'; | ||
|
|
||
| import '../../controllers/auth_state_controller.dart'; | ||
|
|
@@ -22,6 +24,7 @@ class EditProfileScreen extends StatelessWidget { | |
| final AuthStateController authStateController = Get.put( | ||
| AuthStateController(), | ||
| ); | ||
| final debouncer = Debouncer(milliseconds: 800); | ||
|
|
||
| @override | ||
| Widget build(BuildContext context) { | ||
|
|
@@ -30,9 +33,7 @@ class EditProfileScreen extends StatelessWidget { | |
| !(editProfileController.isLoading.value || | ||
| editProfileController.isThereUnsavedChanges()), | ||
| onPopInvokedWithResult: (didPop, result) async { | ||
| if (didPop) { | ||
| return; | ||
| } | ||
| if (didPop) return; | ||
|
|
||
| if (!editProfileController.isLoading.value && | ||
| editProfileController.isThereUnsavedChanges()) { | ||
|
|
@@ -112,16 +113,25 @@ class EditProfileScreen extends StatelessWidget { | |
| keyboardType: TextInputType.text, | ||
| autocorrect: false, | ||
| decoration: InputDecoration( | ||
| // hintText: "Name", | ||
| labelText: AppLocalizations.of(context)!.name, | ||
| prefixIcon: Icon(Icons.abc_rounded), | ||
| ), | ||
| ), | ||
| SizedBox(height: UiSizes.height_20), | ||
| Obx( | ||
| () => TextFormField( | ||
| autovalidateMode: AutovalidateMode.onUserInteraction, | ||
| maxLength: 36, | ||
| validator: (value) { | ||
| if (value!.length > 5) { | ||
| if (value!.length >= 7) { | ||
| final validUsername = RegExp( | ||
| r'^[a-zA-Z0-9._-]+$', | ||
| ).hasMatch(value.trim()); | ||
| if (!validUsername) { | ||
| return AppLocalizations.of( | ||
| context, | ||
| )!.usernameInvalidFormat; | ||
| } | ||
| return null; | ||
| } else { | ||
| return AppLocalizations.of( | ||
|
|
@@ -130,23 +140,54 @@ class EditProfileScreen extends StatelessWidget { | |
| } | ||
| }, | ||
| controller: controller.usernameController, | ||
| onChanged: (value) async { | ||
| if (value.length > 5) { | ||
| controller.usernameAvailable.value = | ||
| await controller.isUsernameAvailable( | ||
| value.trim(), | ||
| ); | ||
| } else { | ||
| onChanged: (value) { | ||
| Get.closeCurrentSnackbar(); | ||
M4dhav marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if (value.length < 7) { | ||
| controller.usernameAvailable.value = false; | ||
| return; | ||
| } | ||
|
|
||
| controller.usernameChecking.value = true; | ||
| controller.usernameAvailable.value = false; | ||
|
|
||
| debouncer.run(() async { | ||
| final available = await controller | ||
| .isUsernameAvailable(value.trim()); | ||
|
|
||
| controller.usernameChecking.value = false; | ||
| controller.usernameAvailable.value = available; | ||
|
|
||
| if (!available) { | ||
| customSnackbar( | ||
| AppLocalizations.of( | ||
| context, | ||
| )!.usernameUnavailable, | ||
| AppLocalizations.of( | ||
| context, | ||
| )!.usernameAlreadyTaken, | ||
| LogType.error, | ||
| snackbarDuration: 1, | ||
| ); | ||
| } | ||
| }); | ||
M4dhav marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }, | ||
|
|
||
| keyboardType: TextInputType.text, | ||
| autocorrect: false, | ||
| decoration: InputDecoration( | ||
| // hintText: "Username", | ||
| labelText: AppLocalizations.of(context)!.username, | ||
| prefixIcon: const Icon(Icons.person), | ||
| suffixIcon: controller.usernameAvailable.value | ||
| suffixIcon: | ||
| !controller.usernameChecking.value && | ||
| controller.usernameAvailable.value && | ||
| controller.usernameController.text | ||
| .trim() | ||
| .length >= | ||
| 7 && | ||
| RegExp(r'^[a-zA-Z0-9._-]+$').hasMatch( | ||
| controller.usernameController.text.trim(), | ||
| ) | ||
| ? const Icon( | ||
| Icons.verified_outlined, | ||
| color: Colors.green, | ||
|
|
@@ -182,11 +223,20 @@ class EditProfileScreen extends StatelessWidget { | |
| () => SizedBox( | ||
| width: double.maxFinite, | ||
| child: ElevatedButton( | ||
| onPressed: () async { | ||
| if (!controller.isLoading.value) { | ||
| await controller.saveProfile(); | ||
| } | ||
| }, | ||
| onPressed: | ||
| (!controller.isLoading.value && | ||
| controller.usernameAvailable.value && | ||
| controller.usernameController.text | ||
| .trim() | ||
| .length >= | ||
| 7 && | ||
| RegExp(r'^[a-zA-Z0-9._-]+$').hasMatch( | ||
| controller.usernameController.text.trim(), | ||
| )) | ||
|
||
| ? () async { | ||
| await controller.saveProfile(); | ||
| } | ||
| : null, | ||
| child: controller.isLoading.value | ||
| ? Center( | ||
| child: | ||
|
|
@@ -298,12 +348,9 @@ class EditProfileScreen extends StatelessWidget { | |
| Column( | ||
| children: [ | ||
| IconButton( | ||
| tooltip: AppLocalizations.of( | ||
| context, | ||
| )!.clickPictureCamera, | ||
| tooltip: AppLocalizations.of(context)!.clickPictureCamera, | ||
| onPressed: () { | ||
| Navigator.pop(context); | ||
| // Display Loading Dialog | ||
| loadingDialog(context); | ||
| editProfileController.pickImageFromCamera(); | ||
| }, | ||
|
|
@@ -322,8 +369,6 @@ class EditProfileScreen extends StatelessWidget { | |
| tooltip: AppLocalizations.of(context)!.pickImageGallery, | ||
| onPressed: () { | ||
| Navigator.pop(context); | ||
|
|
||
| // Display Loading Dialog | ||
| loadingDialog(context); | ||
| editProfileController.pickImageFromGallery(); | ||
| }, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.