Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/controllers/edit_profile_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +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;
Expand Down
2 changes: 1 addition & 1 deletion lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -928,7 +928,7 @@
"@stable": {
"description": "A label indicating a stable, non-beta version of the app."
},
"usernameCharacterLimit": "Username should contain more than 5 characters.",
"usernameCharacterLimit": "Username should contain more than 7 characters.",
"@usernameCharacterLimit": {
"description": "Error message when a chosen username is too short."
},
Expand Down
76 changes: 57 additions & 19 deletions lib/views/screens/edit_profile_screen.dart
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';
Expand All @@ -22,6 +24,7 @@ class EditProfileScreen extends StatelessWidget {
final AuthStateController authStateController = Get.put(
AuthStateController(),
);
final debouncer = Debouncer(milliseconds: 800);

@override
Widget build(BuildContext context) {
Expand All @@ -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()) {
Expand Down Expand Up @@ -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,
)!.usernameInvalidOrTaken;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make a separate string for invalid username and taken username so user can have proper feedback

}
return null;
} else {
return AppLocalizations.of(
Expand All @@ -131,22 +141,55 @@ class EditProfileScreen extends StatelessWidget {
},
controller: controller.usernameController,
onChanged: (value) async {
if (value.length > 5) {
controller.usernameAvailable.value =
await controller.isUsernameAvailable(
value.trim(),
Get.closeCurrentSnackbar();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are no longer opening snackbars for validation then this is unnecessary

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are still showing snackbars for validation errors (invalid format and username taken), so Get.closeCurrentSnackbar() is needed isnt

Copy link
Contributor

@M4dhav M4dhav Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but you said AutoValidator will handle those inline so the only snackbar is shown when the submit button is pressed it looks like


if (value.length >= 7) {
final validUsername = RegExp(
r'^[a-zA-Z0-9._-]+$',
).hasMatch(value.trim());

if (!validUsername || value.trim().length > 36) {
controller.usernameAvailable.value = false;
controller.usernameChecking.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,
)!.usernameInvalidOrTaken,
LogType.error,
snackbarDuration: 1,
);
}
});
} else {
controller.usernameAvailable.value = false;
controller.usernameChecking.value = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are unnecessary as you are already settings the values above

}
},
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
? const Icon(
Icons.verified_outlined,
color: Colors.green,
Expand Down Expand Up @@ -298,12 +341,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();
},
Expand All @@ -322,8 +362,6 @@ class EditProfileScreen extends StatelessWidget {
tooltip: AppLocalizations.of(context)!.pickImageGallery,
onPressed: () {
Navigator.pop(context);

// Display Loading Dialog
loadingDialog(context);
editProfileController.pickImageFromGallery();
},
Expand Down Expand Up @@ -358,4 +396,4 @@ class EditProfileScreen extends StatelessWidget {
],
);
}
}
}