Skip to content

Commit

Permalink
feat(account): ability to update account email and password
Browse files Browse the repository at this point in the history
  • Loading branch information
JagandeepBrar committed Apr 6, 2022
1 parent 30fc640 commit 5ea6269
Show file tree
Hide file tree
Showing 13 changed files with 323 additions and 8 deletions.
10 changes: 10 additions & 0 deletions assets/localization/en.json
Expand Up @@ -327,6 +327,7 @@
"settings.ConnectionDetails": "Connection Details",
"settings.ConnectionDetailsDescription": "Connection Details for {}",
"settings.ConnectionTestFailed": "Connection Test Failed",
"settings.CurrentPassword": "Current Password",
"settings.Custom": "Custom…",
"settings.CustomHeader": "Custom Header",
"settings.CustomHeaders": "Custom Headers",
Expand Down Expand Up @@ -378,10 +379,14 @@
"settings.EmailSentFailure": "Failed to Reset Password",
"settings.EmailSentSuccess": "Email Sent",
"settings.EmailSentSuccessMessage": "An email to reset your password has been sent!",
"settings.EmailUpdated": "Email Updated",
"settings.EmailValidation": "Invalid Email Address",
"settings.EnableModule": "Enable {}",
"settings.EnabledProfile": "Enabled Profile",
"settings.EncryptionKey": "Encryption Key",
"settings.FailedToDeleteAccount": "Failed to Delete Account",
"settings.FailedToUpdateEmail": "Failed to Update Email",
"settings.FailedToUpdatePassword": "Failed to Update Password",
"settings.FeedbackBoard": "Feedback Board",
"settings.FeedbackBoardDescription": "Request New Features",
"settings.FilterCategory": "Filter Category",
Expand Down Expand Up @@ -425,6 +430,7 @@
"settings.MustBeValueBetween": "Must be a value between {} and {}",
"settings.Network": "Network",
"settings.NetworkDescription": "Customize Network Features",
"settings.NewPassword": "New Password",
"settings.Notifications": "Notifications",
"settings.NotificationsDescription": "Set up Webhooks for Push Notifications",
"settings.NoBackupsFound": "No Backups Found",
Expand All @@ -433,9 +439,11 @@
"settings.OpenLinksIn": "Open Links In…",
"settings.Password": "Password",
"settings.PasswordHint1": "If your password includes special characters, considering adding a basic authentication header with your username and password instead for better support",
"settings.PasswordUpdated": "Password Updated",
"settings.PasswordValidation": "Password Required",
"settings.PastDays": "Past Days",
"settings.PastDaysInScheduleView": "Past Days In Schedule View",
"settings.PleaseSignInAgain": "Please sign in again",
"settings.Profiles": "Profiles",
"settings.ProfilesBannerLine1": "Profiles allow you to add multiple instances of modules into LunaSea. You can switch between profiles in the main navigation drawer.",
"settings.ProfilesBannerLine2": "Newznab indexer searching and external modules are enabled and shared across all profiles.",
Expand Down Expand Up @@ -487,6 +495,8 @@
"settings.TestConnection": "Test Connection",
"settings.TLSCertificateValidation": "TLS Certificate Validation",
"settings.TLSCertificateValidationDescription": "Validate Certificates in TLS Connections",
"settings.UpdateEmail": "Update Email",
"settings.UpdatePassword": "Update Password",
"settings.Use24HourTime": "Use 24 Hour Time",
"settings.Use24HourTimeDescription": "Show Timestamps in 24 Hour Style",
"settings.Username": "Username",
Expand Down
40 changes: 40 additions & 0 deletions lib/core/firebase/auth.dart
Expand Up @@ -87,6 +87,46 @@ class LunaFirebaseAuth {
}
}

Future<LunaFirebaseResponse> updateEmail(
String newEmail,
String password,
) async {
try {
return await user!
.reauthenticateWithCredential(EmailAuthProvider.credential(
email: email!,
password: password,
))
.then((credentials) => credentials.user!.updateEmail(newEmail))
.then((_) => LunaFirebaseResponse(state: true));
} on FirebaseAuthException catch (error) {
return LunaFirebaseResponse(state: false, error: error);
} catch (error, stack) {
LunaLogger().error('Failed to set email: ${user!.email}', error, stack);
rethrow;
}
}

Future<LunaFirebaseResponse> updatePassword(
String newPassword,
String password,
) async {
try {
return await user!
.reauthenticateWithCredential(EmailAuthProvider.credential(
email: email!,
password: password,
))
.then((credentials) => credentials.user!.updatePassword(newPassword))
.then((_) => LunaFirebaseResponse(state: true));
} on FirebaseAuthException catch (error) {
return LunaFirebaseResponse(state: false, error: error);
} catch (error, stack) {
LunaLogger().error('Failed to set pass: ${user!.email}', error, stack);
rethrow;
}
}

/// Reset a user's password by sending them a password reset email.
Future<void> resetPassword(String email) async {
instance.sendPasswordResetEmail(email: email);
Expand Down
Expand Up @@ -41,7 +41,7 @@ class IO implements LunaWindowManager {
const minSize = Size(min, min);
const initSize = Size(init, init);

await windowManager.setSize(initSize);
if (!kDebugMode) await windowManager.setSize(initSize);
// Currently broken on Linux
if (!LunaPlatform().isLinux) {
await windowManager.setMinimumSize(minSize);
Expand Down
2 changes: 2 additions & 0 deletions lib/core/ui/icons/icon.dart
Expand Up @@ -11,13 +11,15 @@ class LunaIcons {
static const IconData DELETE = Icons.delete_rounded;
static const IconData DEVICES = Icons.devices_rounded;
static const IconData DOWNLOAD = Icons.file_download_rounded;
static const IconData EMAIL = Icons.email_rounded;
static const IconData ERROR = Icons.error_rounded;
static const IconData FILTER = Icons.filter_list_rounded;
static const IconData HISTORY = Icons.history_rounded;
static const IconData HOME = Icons.home_rounded;
static const IconData MONITOR_OFF = Icons.turned_in_not_rounded;
static const IconData MONITOR_ON = Icons.turned_in_rounded;
static const IconData MUSIC = Icons.music_note_rounded;
static const IconData PASSWORD = Icons.vpn_key_rounded;
static const IconData PLAY = Icons.play_arrow_rounded;
static const IconData PROFILES = Icons.switch_account_rounded;
static const IconData REFRESH = Icons.refresh_rounded;
Expand Down
120 changes: 120 additions & 0 deletions lib/modules/settings/core/dialogs.dart
@@ -1,3 +1,4 @@
import 'package:email_validator/email_validator.dart';
import 'package:flutter/material.dart';
import 'package:lunasea/modules/settings/core/types/header.dart';
import 'package:tuple/tuple.dart';
Expand Down Expand Up @@ -556,6 +557,125 @@ class SettingsDialogs {
return Tuple2(_flag, _textController.text);
}

Future<Tuple3<bool, String, String>> updateAccountEmail(
BuildContext context,
) async {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _flag = false;

void _setValues(bool flag) {
if (_formKey.currentState!.validate()) {
_flag = flag;
Navigator.of(context).pop();
}
}

await LunaDialog.dialog(
context: context,
title: 'settings.UpdateEmail'.tr(),
buttons: [
LunaDialog.button(
text: 'lunasea.Update'.tr(),
onPressed: () => _setValues(true),
),
],
content: [
Form(
key: _formKey,
child: Column(
children: [
LunaDialog.textFormInput(
controller: _emailController,
title: 'settings.Email'.tr(),
onSubmitted: (_) => _setValues(true),
validator: (value) {
return EmailValidator.validate(value ?? '')
? null
: 'settings.EmailValidation'.tr();
},
),
LunaDialog.textFormInput(
controller: _passwordController,
title: 'settings.CurrentPassword'.tr(),
obscureText: true,
onSubmitted: (_) => _setValues(true),
validator: (value) {
return value?.isEmpty ?? true
? 'settings.PasswordValidation'.tr()
: null;
},
),
],
),
),
],
contentPadding: LunaDialog.textDialogContentPadding(),
);
return Tuple3(_flag, _emailController.text, _passwordController.text);
}

Future<Tuple3<bool, String, String>> updateAccountPassword(
BuildContext context,
) async {
final _formKey = GlobalKey<FormState>();
final _currentPassController = TextEditingController();
final _newPassController = TextEditingController();
bool _flag = false;

void _setValues(bool flag) {
if (_formKey.currentState!.validate()) {
_flag = flag;
Navigator.of(context).pop();
}
}

await LunaDialog.dialog(
context: context,
title: 'settings.UpdatePassword'.tr(),
buttons: [
LunaDialog.button(
text: 'lunasea.Update'.tr(),
onPressed: () => _setValues(true),
),
],
content: [
Form(
key: _formKey,
child: Column(
children: [
LunaDialog.textFormInput(
controller: _currentPassController,
title: 'settings.CurrentPassword'.tr(),
obscureText: true,
onSubmitted: (_) => _setValues(true),
validator: (value) {
return value?.isEmpty ?? true
? 'settings.PasswordValidation'.tr()
: null;
},
),
LunaDialog.textFormInput(
controller: _newPassController,
title: 'settings.NewPassword'.tr(),
obscureText: true,
onSubmitted: (_) => _setValues(true),
validator: (value) {
return value?.isEmpty ?? true
? 'settings.PasswordValidation'.tr()
: null;
},
),
],
),
),
],
contentPadding: LunaDialog.textDialogContentPadding(),
);
return Tuple3(_flag, _newPassController.text, _currentPassController.text);
}

Future<Tuple2<bool, String>> addProfile(
BuildContext context,
List<String?> profiles,
Expand Down
12 changes: 9 additions & 3 deletions lib/modules/settings/routes/account/pages/settings.dart
Expand Up @@ -2,6 +2,10 @@ import 'package:flutter/material.dart';
import 'package:lunasea/core.dart';
import 'package:lunasea/modules/settings.dart';

import '../widgets/change_email_tile.dart';
import '../widgets/change_password_tile.dart';
import '../widgets/delete_account_tile.dart';

class SettingsAccountSettingsRouter extends SettingsPageRouter {
SettingsAccountSettingsRouter() : super('/settings/account/settings');

Expand All @@ -27,12 +31,12 @@ class _State extends State<_SettingsAccountSettingsRoute>
Widget build(BuildContext context) {
return LunaScaffold(
scaffoldKey: _scaffoldKey,
appBar: _appBar() as PreferredSizeWidget?,
appBar: _appBar(),
body: _body(),
);
}

Widget _appBar() {
PreferredSizeWidget _appBar() {
return LunaAppBar(
title: 'settings.AccountSettings'.tr(),
scrollControllers: [scrollController],
Expand All @@ -43,7 +47,9 @@ class _State extends State<_SettingsAccountSettingsRoute>
return LunaListView(
controller: scrollController,
children: const [
SettingsAccountDeleteAccountTile(),
ChangeEmailTile(),
ChangePasswordTile(),
DeleteAccountTile(),
],
);
}
Expand Down
1 change: 0 additions & 1 deletion lib/modules/settings/routes/account/widgets.dart
@@ -1,4 +1,3 @@
export 'widgets/config_backup_tile.dart';
export 'widgets/config_delete_tile.dart';
export 'widgets/config_restore_tile.dart';
export 'widgets/delete_account_tile.dart';
59 changes: 59 additions & 0 deletions lib/modules/settings/routes/account/widgets/change_email_tile.dart
@@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
import 'package:lunasea/core.dart';
import 'package:lunasea/modules/settings.dart';

class ChangeEmailTile extends StatefulWidget {
const ChangeEmailTile({
Key? key,
}) : super(key: key);

@override
State<StatefulWidget> createState() => _State();
}

class _State extends State<ChangeEmailTile> {
LunaLoadingState _loadingState = LunaLoadingState.INACTIVE;

void updateState(LunaLoadingState state) {
if (mounted) setState(() => _loadingState = state);
}

@override
Widget build(BuildContext context) {
return LunaBlock(
title: 'settings.UpdateEmail'.tr(),
trailing: LunaIconButton(
icon: LunaIcons.EMAIL,
loadingState: _loadingState,
),
onTap: _update,
);
}

Future<void> _update() async {
if (_loadingState == LunaLoadingState.ACTIVE) return;
updateState(LunaLoadingState.ACTIVE);

Tuple3<bool, String, String> _result =
await SettingsDialogs().updateAccountEmail(context);
if (_result.item1) {
await LunaFirebaseAuth()
.updateEmail(_result.item2, _result.item3)
.then((res) {
if (res.state) {
showLunaSuccessSnackBar(
title: 'settings.EmailUpdated'.tr(),
message: _result.item2,
);
} else {
updateState(LunaLoadingState.INACTIVE);
showLunaErrorSnackBar(
title: 'settings.FailedToUpdateEmail'.tr(),
message: res.error?.message ?? 'lunasea.UnknownError'.tr(),
);
}
});
}
updateState(LunaLoadingState.INACTIVE);
}
}

0 comments on commit 5ea6269

Please sign in to comment.