mirror of
https://github.com/AppFlowy-IO/AppFlowy
synced 2026-05-24 01:28:24 +00:00
feat: support account deletion (#6103)
* feat: support account deletion * chore: update translation * feat: support account deletion on mobile * fix: only display account deletion button in appflowy cloud mode * chore: remove unused code * chore: update checkbox style * fix: integration test * chore: update translations * chore: update sentry version in podfile.lock * chore: update version
This commit is contained in:
parent
a6cce62b06
commit
9da71c3186
23 changed files with 936 additions and 471 deletions
|
|
@ -26,7 +26,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
|
|||
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
|
||||
CARGO_MAKE_CRATE_NAME = "dart-ffi"
|
||||
LIB_NAME = "dart_ffi"
|
||||
APPFLOWY_VERSION = "0.6.8"
|
||||
APPFLOWY_VERSION = "0.6.9"
|
||||
FLUTTER_DESKTOP_FEATURES = "dart"
|
||||
PRODUCT_NAME = "AppFlowy"
|
||||
MACOSX_DEPLOYMENT_TARGET = "11.0"
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@
|
|||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
|
|
@ -12,6 +10,7 @@ import 'package:appflowy/startup/startup.dart';
|
|||
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/account/account.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
|
||||
|
|
@ -19,6 +18,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text_field.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
|
@ -51,12 +51,12 @@ void main() {
|
|||
|
||||
// Scroll to sign-in
|
||||
await tester.scrollUntilVisible(
|
||||
find.byType(SignInOutButton),
|
||||
find.byType(AccountSignInOutButton),
|
||||
100,
|
||||
scrollable: find.findSettingsScrollable(),
|
||||
);
|
||||
|
||||
await tester.tapButton(find.byType(SignInOutButton));
|
||||
await tester.tapButton(find.byType(AccountSignInOutButton));
|
||||
|
||||
// sign up with Google
|
||||
await tester.tapGoogleLoginInButton();
|
||||
|
|
@ -68,7 +68,7 @@ void main() {
|
|||
|
||||
// Scroll to sign-out
|
||||
await tester.scrollUntilVisible(
|
||||
find.byType(SignInOutButton),
|
||||
find.byType(AccountSignInOutButton),
|
||||
100,
|
||||
scrollable: find.findSettingsScrollable(),
|
||||
);
|
||||
|
|
@ -85,7 +85,7 @@ void main() {
|
|||
await tester.openSettings();
|
||||
await tester.openSettingsPage(SettingsPage.account);
|
||||
final userNameInput =
|
||||
tester.widget(find.byType(UserProfileSetting)) as UserProfileSetting;
|
||||
tester.widget(find.byType(AccountUserProfile)) as AccountUserProfile;
|
||||
expect(userNameInput.name, 'Me');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import 'package:appflowy/startup/startup.dart';
|
|||
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/account/account.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
|
@ -41,11 +42,11 @@ void main() {
|
|||
|
||||
// Scroll to sign-out
|
||||
await tester.scrollUntilVisible(
|
||||
find.byType(SignInOutButton),
|
||||
find.byType(AccountSignInOutButton),
|
||||
100,
|
||||
scrollable: find.findSettingsScrollable(),
|
||||
);
|
||||
await tester.tapButton(find.byType(SignInOutButton));
|
||||
await tester.tapButton(find.byType(AccountSignInOutButton));
|
||||
|
||||
tester.expectToSeeText(LocaleKeys.button_ok.tr());
|
||||
await tester.tapButtonWithName(LocaleKeys.button_ok.tr());
|
||||
|
|
@ -67,11 +68,11 @@ void main() {
|
|||
|
||||
// Scroll to sign-in
|
||||
await tester.scrollUntilVisible(
|
||||
find.byType(SignInOutButton),
|
||||
find.byType(AccountSignInOutButton),
|
||||
100,
|
||||
scrollable: find.findSettingsScrollable(),
|
||||
);
|
||||
await tester.tapButton(find.byType(SignInOutButton));
|
||||
await tester.tapButton(find.byType(AccountSignInOutButton));
|
||||
|
||||
tester.expectToSeeGoogleLoginButton();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
|
|
@ -11,6 +9,7 @@ import 'package:appflowy/startup/startup.dart';
|
|||
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/account/account_user_profile.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart';
|
||||
|
|
@ -18,6 +17,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text_field.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
|
@ -67,7 +67,7 @@ void main() {
|
|||
|
||||
// Verify name
|
||||
final profileSetting =
|
||||
tester.widget(find.byType(UserProfileSetting)) as UserProfileSetting;
|
||||
tester.widget(find.byType(AccountUserProfile)) as AccountUserProfile;
|
||||
|
||||
expect(profileSetting.name, name);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/account/account.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
|
@ -20,12 +20,12 @@ extension AppFlowyAuthTest on WidgetTester {
|
|||
Future<void> logout() async {
|
||||
final scrollable = find.findSettingsScrollable();
|
||||
await scrollUntilVisible(
|
||||
find.byType(SignInOutButton),
|
||||
find.byType(AccountSignInOutButton),
|
||||
100,
|
||||
scrollable: scrollable,
|
||||
);
|
||||
|
||||
await tapButton(find.byType(SignInOutButton));
|
||||
await tapButton(find.byType(AccountSignInOutButton));
|
||||
|
||||
expectToSeeText(LocaleKeys.button_ok.tr());
|
||||
await tapButtonWithName(LocaleKeys.button_ok.tr());
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
|||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_setting.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/account/account_user_profile.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_workspace_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu_element.dart';
|
||||
|
|
@ -77,14 +77,14 @@ extension AppFlowySettings on WidgetTester {
|
|||
Future<void> enterUserName(String name) async {
|
||||
// Enable editing username
|
||||
final editUsernameFinder = find.descendant(
|
||||
of: find.byType(UserProfileSetting),
|
||||
of: find.byType(AccountUserProfile),
|
||||
matching: find.byFlowySvg(FlowySvgs.edit_s),
|
||||
);
|
||||
await tap(editUsernameFinder, warnIfMissed: false);
|
||||
await pumpAndSettle();
|
||||
|
||||
final userNameFinder = find.descendant(
|
||||
of: find.byType(UserProfileSetting),
|
||||
of: find.byType(AccountUserProfile),
|
||||
matching: find.byType(FlowyTextField),
|
||||
);
|
||||
await enterText(userNameFinder, name);
|
||||
|
|
|
|||
|
|
@ -67,11 +67,11 @@ PODS:
|
|||
- SDWebImage (5.14.2):
|
||||
- SDWebImage/Core (= 5.14.2)
|
||||
- SDWebImage/Core (5.14.2)
|
||||
- Sentry/HybridSDK (8.33.0)
|
||||
- sentry_flutter (8.7.0):
|
||||
- Sentry/HybridSDK (8.35.1)
|
||||
- sentry_flutter (8.8.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- Sentry/HybridSDK (= 8.33.0)
|
||||
- Sentry/HybridSDK (= 8.35.1)
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
|
|
@ -184,8 +184,8 @@ SPEC CHECKSUMS:
|
|||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
SDWebImage: b9a731e1d6307f44ca703b3976d18c24ca561e84
|
||||
Sentry: 8560050221424aef0bebc8e31eedf00af80f90a6
|
||||
sentry_flutter: e26b861f744e5037a3faf9bf56603ec65d658a61
|
||||
Sentry: 1fe34e9c2cbba1e347623610d26db121dcb569f1
|
||||
sentry_flutter: a39c2a2d67d5e5b9cb0b94a4985c76dd5b3fc737
|
||||
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
|
||||
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
|
||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||
|
|
|
|||
|
|
@ -46,7 +46,9 @@ class MobileHomePageHeader extends StatelessWidget {
|
|||
? _MobileWorkspace(userProfile: userProfile)
|
||||
: _MobileUser(userProfile: userProfile),
|
||||
),
|
||||
const HomePageSettingsPopupMenu(),
|
||||
HomePageSettingsPopupMenu(
|
||||
userProfile: userProfile,
|
||||
),
|
||||
const HSpace(8.0),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ class _MobileHomeSettingPageState extends State<MobileHomeSettingPage> {
|
|||
const SupportSettingGroup(),
|
||||
const AboutSettingGroup(),
|
||||
UserSessionSettingGroup(
|
||||
userProfile: userProfile,
|
||||
showThirdPartyLogin: showThirdPartyLogin,
|
||||
),
|
||||
const VSpace(20),
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
|||
import 'package:appflowy/mobile/presentation/presentation.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/workspace/invite_members_screen.dart';
|
||||
import 'package:appflowy/shared/popup_menu/appflowy_popup_menu.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart'
|
||||
|
|
@ -18,7 +19,12 @@ enum _MobileSettingsPopupMenuItem {
|
|||
}
|
||||
|
||||
class HomePageSettingsPopupMenu extends StatelessWidget {
|
||||
const HomePageSettingsPopupMenu({super.key});
|
||||
const HomePageSettingsPopupMenu({
|
||||
super.key,
|
||||
required this.userProfile,
|
||||
});
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -41,12 +47,15 @@ class HomePageSettingsPopupMenu extends StatelessWidget {
|
|||
svg: FlowySvgs.m_notification_settings_s,
|
||||
text: LocaleKeys.settings_popupMenuItem_settings.tr(),
|
||||
),
|
||||
const PopupMenuDivider(height: 0.5),
|
||||
_buildItem(
|
||||
value: _MobileSettingsPopupMenuItem.members,
|
||||
svg: FlowySvgs.m_settings_member_s,
|
||||
text: LocaleKeys.settings_popupMenuItem_members.tr(),
|
||||
),
|
||||
// only show the member items in cloud mode
|
||||
if (userProfile.authenticator == AuthenticatorPB.AppFlowyCloud) ...[
|
||||
const PopupMenuDivider(height: 0.5),
|
||||
_buildItem(
|
||||
value: _MobileSettingsPopupMenuItem.members,
|
||||
svg: FlowySvgs.m_settings_member_s,
|
||||
text: LocaleKeys.settings_popupMenuItem_members.tr(),
|
||||
),
|
||||
],
|
||||
const PopupMenuDivider(height: 0.5),
|
||||
_buildItem(
|
||||
value: _MobileSettingsPopupMenuItem.trash,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/application/sign_in_bloc.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/account/account_deletion.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -13,60 +17,199 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||
class UserSessionSettingGroup extends StatelessWidget {
|
||||
const UserSessionSettingGroup({
|
||||
super.key,
|
||||
required this.userProfile,
|
||||
required this.showThirdPartyLogin,
|
||||
});
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
final bool showThirdPartyLogin;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
if (showThirdPartyLogin) ...[
|
||||
BlocProvider(
|
||||
create: (context) => getIt<SignInBloc>(),
|
||||
child: BlocConsumer<SignInBloc, SignInState>(
|
||||
listener: (context, state) {
|
||||
state.successOrFail?.fold(
|
||||
(result) => runAppFlowy(),
|
||||
(e) => Log.error(e),
|
||||
);
|
||||
},
|
||||
builder: (context, state) {
|
||||
return const ThirdPartySignInButtons(
|
||||
expanded: true,
|
||||
);
|
||||
},
|
||||
// third party sign in buttons
|
||||
if (showThirdPartyLogin) _buildThirdPartySignInButtons(context),
|
||||
const VSpace(8.0),
|
||||
|
||||
// logout button
|
||||
MobileLogoutButton(
|
||||
text: LocaleKeys.settings_menu_logout.tr(),
|
||||
onPressed: () async => _showLogoutDialog(),
|
||||
),
|
||||
|
||||
// delete account button
|
||||
// only show the delete account button in cloud mode
|
||||
if (userProfile.authenticator == AuthenticatorPB.AppFlowyCloud) ...[
|
||||
const VSpace(16.0),
|
||||
MobileLogoutButton(
|
||||
text: LocaleKeys.button_deleteAccount.tr(),
|
||||
textColor: Theme.of(context).colorScheme.error,
|
||||
onPressed: () => _showDeleteAccountDialog(context),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildThirdPartySignInButtons(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => getIt<SignInBloc>(),
|
||||
child: BlocConsumer<SignInBloc, SignInState>(
|
||||
listener: (context, state) {
|
||||
state.successOrFail?.fold(
|
||||
(result) => runAppFlowy(),
|
||||
(e) => Log.error(e),
|
||||
);
|
||||
},
|
||||
builder: (context, state) {
|
||||
return const ThirdPartySignInButtons(
|
||||
expanded: true,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showDeleteAccountDialog(BuildContext context) async {
|
||||
return showMobileBottomSheet(
|
||||
context,
|
||||
useRootNavigator: true,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
builder: (_) => const _DeleteAccountBottomSheet(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showLogoutDialog() async {
|
||||
return showFlowyCupertinoConfirmDialog(
|
||||
title: LocaleKeys.settings_menu_logoutPrompt.tr(),
|
||||
leftButton: FlowyText(
|
||||
LocaleKeys.button_cancel.tr(),
|
||||
fontSize: 17.0,
|
||||
figmaLineHeight: 24.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: const Color(0xFF007AFF),
|
||||
),
|
||||
rightButton: FlowyText(
|
||||
LocaleKeys.button_logout.tr(),
|
||||
fontSize: 17.0,
|
||||
figmaLineHeight: 24.0,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: const Color(0xFFFE0220),
|
||||
),
|
||||
onRightButtonPressed: (context) async {
|
||||
Navigator.of(context).pop();
|
||||
await getIt<AuthService>().signOut();
|
||||
await runAppFlowy();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DeleteAccountBottomSheet extends StatefulWidget {
|
||||
const _DeleteAccountBottomSheet();
|
||||
|
||||
@override
|
||||
State<_DeleteAccountBottomSheet> createState() =>
|
||||
_DeleteAccountBottomSheetState();
|
||||
}
|
||||
|
||||
class _DeleteAccountBottomSheetState extends State<_DeleteAccountBottomSheet> {
|
||||
final emailController = TextEditingController();
|
||||
final isChecked = ValueNotifier(false);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
emailController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const VSpace(18.0),
|
||||
const FlowySvg(
|
||||
FlowySvgs.icon_warning_xl,
|
||||
blendMode: null,
|
||||
),
|
||||
const VSpace(12.0),
|
||||
FlowyText(
|
||||
LocaleKeys.newSettings_myAccount_deleteAccount_title.tr(),
|
||||
fontSize: 20.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
const VSpace(12.0),
|
||||
FlowyText(
|
||||
LocaleKeys.newSettings_myAccount_deleteAccount_confirmHint1.tr(),
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w400,
|
||||
maxLines: 10,
|
||||
),
|
||||
const VSpace(18.0),
|
||||
SizedBox(
|
||||
height: 36.0,
|
||||
child: FlowyTextField(
|
||||
controller: emailController,
|
||||
textStyle: const TextStyle(fontSize: 14.0),
|
||||
hintStyle: const TextStyle(fontSize: 14.0),
|
||||
hintText: LocaleKeys.settings_user_email.tr(),
|
||||
),
|
||||
),
|
||||
const VSpace(8),
|
||||
const VSpace(18.0),
|
||||
_buildCheckbox(),
|
||||
const VSpace(18.0),
|
||||
MobileLogoutButton(
|
||||
text: LocaleKeys.button_deleteAccount.tr(),
|
||||
textColor: Theme.of(context).colorScheme.error,
|
||||
onPressed: () => deleteMyAccount(
|
||||
context,
|
||||
emailController.text.trim(),
|
||||
isChecked.value,
|
||||
),
|
||||
),
|
||||
const VSpace(12.0),
|
||||
MobileLogoutButton(
|
||||
text: LocaleKeys.button_cancel.tr(),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
const VSpace(36.0),
|
||||
],
|
||||
MobileSignInOrLogoutButton(
|
||||
labelText: LocaleKeys.settings_menu_logout.tr(),
|
||||
onPressed: () async {
|
||||
await showFlowyCupertinoConfirmDialog(
|
||||
title: LocaleKeys.settings_menu_logoutPrompt.tr(),
|
||||
leftButton: FlowyText(
|
||||
LocaleKeys.button_cancel.tr(),
|
||||
fontSize: 17.0,
|
||||
figmaLineHeight: 24.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: const Color(0xFF007AFF),
|
||||
),
|
||||
rightButton: FlowyText(
|
||||
LocaleKeys.button_logout.tr(),
|
||||
fontSize: 17.0,
|
||||
figmaLineHeight: 24.0,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: const Color(0xFFFE0220),
|
||||
),
|
||||
onRightButtonPressed: (context) async {
|
||||
Navigator.of(context).pop();
|
||||
await getIt<AuthService>().signOut();
|
||||
await runAppFlowy();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCheckbox() {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => isChecked.value = !isChecked.value,
|
||||
child: ValueListenableBuilder<bool>(
|
||||
valueListenable: isChecked,
|
||||
builder: (context, isChecked, _) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(1.0),
|
||||
child: FlowySvg(
|
||||
isChecked ? FlowySvgs.check_filled_s : FlowySvgs.uncheck_s,
|
||||
size: const Size.square(16.0),
|
||||
blendMode: isChecked ? null : BlendMode.srcIn,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const HSpace(6.0),
|
||||
Expanded(
|
||||
child: FlowyText.regular(
|
||||
LocaleKeys.newSettings_myAccount_deleteAccount_confirmHint2.tr(),
|
||||
fontSize: 14.0,
|
||||
figmaLineHeight: 18.0,
|
||||
maxLines: 3,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:appflowy/workspace/application/settings/plan/workspace_subscription_ext.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
|
|
@ -9,6 +7,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart';
|
|||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
abstract class IUserBackendService {
|
||||
Future<FlowyResult<void, FlowyError>> cancelSubscription(
|
||||
|
|
@ -292,4 +291,9 @@ class UserBackendService implements IUserBackendService {
|
|||
|
||||
return UserEventUpdateWorkspaceSubscriptionPaymentPeriod(request).send();
|
||||
}
|
||||
|
||||
// NOTE: This function is irreversible and will delete the current user's account.
|
||||
static Future<FlowyResult<void, FlowyError>> deleteCurrentAccount() {
|
||||
return UserEventDeleteAccount().send();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,16 +2,18 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
|||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MobileSignInOrLogoutButton extends StatelessWidget {
|
||||
const MobileSignInOrLogoutButton({
|
||||
class MobileLogoutButton extends StatelessWidget {
|
||||
const MobileLogoutButton({
|
||||
super.key,
|
||||
this.icon,
|
||||
required this.labelText,
|
||||
required this.text,
|
||||
this.textColor,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
final FlowySvgData? icon;
|
||||
final String labelText;
|
||||
final String text;
|
||||
final Color? textColor;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
@override
|
||||
|
|
@ -26,7 +28,7 @@ class MobileSignInOrLogoutButton extends StatelessWidget {
|
|||
Radius.circular(4),
|
||||
),
|
||||
border: Border.all(
|
||||
color: style.colorScheme.outline,
|
||||
color: textColor ?? style.colorScheme.outline,
|
||||
width: 0.5,
|
||||
),
|
||||
),
|
||||
|
|
@ -52,9 +54,10 @@ class MobileSignInOrLogoutButton extends StatelessWidget {
|
|||
const HSpace(8),
|
||||
],
|
||||
FlowyText(
|
||||
labelText,
|
||||
text,
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: textColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -334,8 +334,10 @@ class _ConfirmPopupState extends State<ConfirmPopup> {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildTitle(),
|
||||
const VSpace(6),
|
||||
_buildDescription(),
|
||||
if (widget.description.isNotEmpty) ...[
|
||||
const VSpace(6),
|
||||
_buildDescription(),
|
||||
],
|
||||
if (widget.child != null) ...[
|
||||
const VSpace(12),
|
||||
widget.child!,
|
||||
|
|
@ -376,6 +378,10 @@ class _ConfirmPopupState extends State<ConfirmPopup> {
|
|||
}
|
||||
|
||||
Widget _buildDescription() {
|
||||
if (widget.description.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return FlowyText.regular(
|
||||
widget.description,
|
||||
fontSize: 16.0,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
export 'account_deletion.dart';
|
||||
export 'account_sign_in_out.dart';
|
||||
export 'account_user_profile.dart';
|
||||
|
|
@ -0,0 +1,262 @@
|
|||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/application/user_service.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' show PlatformExtension;
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:toastification/toastification.dart';
|
||||
|
||||
class AccountDeletionButton extends StatefulWidget {
|
||||
const AccountDeletionButton({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AccountDeletionButton> createState() => _AccountDeletionButtonState();
|
||||
}
|
||||
|
||||
class _AccountDeletionButtonState extends State<AccountDeletionButton> {
|
||||
final TextEditingController emailController = TextEditingController();
|
||||
final isCheckedNotifier = ValueNotifier(false);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
emailController.dispose();
|
||||
isCheckedNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textColor = Theme.of(context).brightness == Brightness.light
|
||||
? const Color(0xFF4F4F4F)
|
||||
: const Color(0xFFB0B0B0);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FlowyText(
|
||||
LocaleKeys.button_deleteAccount.tr(),
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
figmaLineHeight: 21.0,
|
||||
color: textColor,
|
||||
),
|
||||
const VSpace(8),
|
||||
Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: FlowyText.regular(
|
||||
LocaleKeys.newSettings_myAccount_deleteAccount_description.tr(),
|
||||
fontSize: 12.0,
|
||||
figmaLineHeight: 13.0,
|
||||
maxLines: 2,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
const HSpace(32),
|
||||
FlowyTextButton(
|
||||
LocaleKeys.button_deleteAccount.tr(),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 26, vertical: 10),
|
||||
fillColor: Colors.transparent,
|
||||
radius: Corners.s12Border,
|
||||
hoverColor: Theme.of(context).colorScheme.error.withOpacity(0.1),
|
||||
fontColor: Theme.of(context).colorScheme.error,
|
||||
fontHoverColor: Colors.white,
|
||||
fontSize: 12,
|
||||
isDangerous: true,
|
||||
lineHeight: 18.0 / 12.0,
|
||||
onPressed: () {
|
||||
isCheckedNotifier.value = false;
|
||||
|
||||
showCancelAndDeleteDialog(
|
||||
context: context,
|
||||
title:
|
||||
LocaleKeys.newSettings_myAccount_deleteAccount_title.tr(),
|
||||
description: '',
|
||||
builder: (_) => _AccountDeletionDialog(
|
||||
emailController: emailController,
|
||||
isChecked: isCheckedNotifier,
|
||||
),
|
||||
onDelete: () => deleteMyAccount(
|
||||
context,
|
||||
emailController.text.trim(),
|
||||
isCheckedNotifier.value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AccountDeletionDialog extends StatelessWidget {
|
||||
const _AccountDeletionDialog({
|
||||
required this.emailController,
|
||||
required this.isChecked,
|
||||
});
|
||||
|
||||
final TextEditingController emailController;
|
||||
final ValueNotifier<bool> isChecked;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FlowyText.regular(
|
||||
LocaleKeys.newSettings_myAccount_deleteAccount_confirmHint1.tr(),
|
||||
fontSize: 14.0,
|
||||
figmaLineHeight: 18.0,
|
||||
maxLines: 2,
|
||||
color: ConfirmPopupColor.descriptionColor(context),
|
||||
),
|
||||
const VSpace(12.0),
|
||||
FlowyTextField(
|
||||
hintText: LocaleKeys.settings_user_email.tr(),
|
||||
controller: emailController,
|
||||
),
|
||||
const VSpace(16),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => isChecked.value = !isChecked.value,
|
||||
child: ValueListenableBuilder<bool>(
|
||||
valueListenable: isChecked,
|
||||
builder: (context, isChecked, _) {
|
||||
return FlowySvg(
|
||||
isChecked ? FlowySvgs.check_filled_s : FlowySvgs.uncheck_s,
|
||||
size: const Size.square(16.0),
|
||||
blendMode: isChecked ? null : BlendMode.srcIn,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const HSpace(6.0),
|
||||
Expanded(
|
||||
child: FlowyText.regular(
|
||||
LocaleKeys.newSettings_myAccount_deleteAccount_confirmHint2
|
||||
.tr(),
|
||||
fontSize: 14.0,
|
||||
figmaLineHeight: 16.0,
|
||||
maxLines: 3,
|
||||
color: ConfirmPopupColor.descriptionColor(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteMyAccount(
|
||||
BuildContext context,
|
||||
String email,
|
||||
bool isChecked,
|
||||
) async {
|
||||
final bottomPadding = PlatformExtension.isMobile
|
||||
? MediaQuery.of(context).viewInsets.bottom
|
||||
: 0.0;
|
||||
|
||||
if (!isChecked) {
|
||||
showToastNotification(
|
||||
context,
|
||||
type: ToastificationType.warning,
|
||||
bottomPadding: bottomPadding,
|
||||
message: LocaleKeys
|
||||
.newSettings_myAccount_deleteAccount_checkToConfirmError
|
||||
.tr(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// fetch the user email from server instead of reading from provider,
|
||||
// this is to avoid the email doesn't match the real user's email
|
||||
final userEmail = await UserBackendService.getCurrentUserProfile()
|
||||
.fold((s) => s.email, (_) => null);
|
||||
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (userEmail == null) {
|
||||
showToastNotification(
|
||||
context,
|
||||
type: ToastificationType.error,
|
||||
bottomPadding: bottomPadding,
|
||||
message: LocaleKeys
|
||||
.newSettings_myAccount_deleteAccount_failedToGetCurrentUser
|
||||
.tr(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (email.isEmpty || email.toLowerCase() != userEmail.toLowerCase()) {
|
||||
showToastNotification(
|
||||
context,
|
||||
type: ToastificationType.warning,
|
||||
bottomPadding: bottomPadding,
|
||||
message: LocaleKeys
|
||||
.newSettings_myAccount_deleteAccount_emailValidationFailed
|
||||
.tr(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final loading = Loading(context)..start();
|
||||
|
||||
await UserBackendService.deleteCurrentAccount().fold(
|
||||
(s) {
|
||||
Log.info('account deletion success, email: $email');
|
||||
|
||||
loading.stop();
|
||||
showToastNotification(
|
||||
context,
|
||||
message: LocaleKeys
|
||||
.newSettings_myAccount_deleteAccount_deleteAccountSuccess
|
||||
.tr(),
|
||||
bottomPadding: bottomPadding,
|
||||
);
|
||||
|
||||
// delay 1 second to make sure the toast notification is shown
|
||||
Future.delayed(const Duration(seconds: 1), () async {
|
||||
// pop to the home screen
|
||||
Navigator.of(context).popUntil((route) {
|
||||
if (route.settings.name == '/') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// restart the application
|
||||
await getIt<AuthService>().signOut();
|
||||
await runAppFlowy();
|
||||
});
|
||||
},
|
||||
(f) {
|
||||
Log.error('account deletion failed, email: $email, error: $f');
|
||||
|
||||
loading.stop();
|
||||
showToastNotification(
|
||||
context,
|
||||
type: ToastificationType.error,
|
||||
bottomPadding: bottomPadding,
|
||||
message: f.msg,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/application/prelude.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/magic_link_sign_in_buttons.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_third_party_login.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class AccountSignInOutButton extends StatelessWidget {
|
||||
const AccountSignInOutButton({
|
||||
super.key,
|
||||
required this.userProfile,
|
||||
required this.onAction,
|
||||
this.signIn = true,
|
||||
});
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
final VoidCallback onAction;
|
||||
final bool signIn;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PrimaryRoundedButton(
|
||||
text: signIn
|
||||
? LocaleKeys.settings_accountPage_login_loginLabel.tr()
|
||||
: LocaleKeys.settings_accountPage_login_logoutLabel.tr(),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||
fontWeight: FontWeight.w600,
|
||||
radius: 12.0,
|
||||
onTap: () =>
|
||||
signIn ? _showSignInDialog(context) : _showLogoutDialog(context),
|
||||
);
|
||||
}
|
||||
|
||||
void _showLogoutDialog(BuildContext context) {
|
||||
showConfirmDialog(
|
||||
context: context,
|
||||
title: LocaleKeys.settings_accountPage_login_logoutLabel.tr(),
|
||||
description: userProfile.encryptionType == EncryptionTypePB.Symmetric
|
||||
? LocaleKeys.settings_menu_selfEncryptionLogoutPrompt.tr()
|
||||
: LocaleKeys.settings_menu_logoutPrompt.tr(),
|
||||
onConfirm: () async {
|
||||
await getIt<AuthService>().signOut();
|
||||
onAction();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showSignInDialog(BuildContext context) async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => BlocProvider<SignInBloc>(
|
||||
create: (context) => getIt<SignInBloc>(),
|
||||
child: const FlowyDialog(
|
||||
constraints: BoxConstraints(maxHeight: 485, maxWidth: 375),
|
||||
child: _SignInDialogContent(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SignInDialogContent extends StatelessWidget {
|
||||
const _SignInDialogContent();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ScaffoldMessenger(
|
||||
child: Scaffold(
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
const _DialogHeader(),
|
||||
const _DialogTitle(),
|
||||
const VSpace(16),
|
||||
const SignInWithMagicLinkButtons(),
|
||||
if (isAuthEnabled) ...[
|
||||
const VSpace(20),
|
||||
const _OrDivider(),
|
||||
const VSpace(10),
|
||||
SettingThirdPartyLogin(
|
||||
didLogin: () {},
|
||||
), // TODO: Pass onAction
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DialogHeader extends StatelessWidget {
|
||||
const _DialogHeader();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
_buildBackButton(context),
|
||||
_buildCloseButton(context),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBackButton(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: Navigator.of(context).pop,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: Row(
|
||||
children: [
|
||||
const FlowySvg(FlowySvgs.arrow_back_m, size: Size.square(24)),
|
||||
const HSpace(8),
|
||||
FlowyText.semibold(LocaleKeys.button_back.tr(), fontSize: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCloseButton(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: Navigator.of(context).pop,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: FlowySvg(
|
||||
FlowySvgs.m_close_m,
|
||||
size: const Size.square(20),
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DialogTitle extends StatelessWidget {
|
||||
const _DialogTitle();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.settings_accountPage_login_loginLabel.tr(),
|
||||
fontSize: 22,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
maxLines: null,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _OrDivider extends StatelessWidget {
|
||||
const _OrDivider();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
const Flexible(child: Divider(thickness: 1)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: FlowyText.regular(LocaleKeys.signIn_or.tr()),
|
||||
),
|
||||
const Flexible(child: Divider(thickness: 1)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
|
||||
import 'package:appflowy/workspace/application/user/settings_user_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_input_field.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
// Account name and account avatar
|
||||
class AccountUserProfile extends StatefulWidget {
|
||||
const AccountUserProfile({
|
||||
super.key,
|
||||
required this.name,
|
||||
required this.iconUrl,
|
||||
this.onSave,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String iconUrl;
|
||||
final void Function(String)? onSave;
|
||||
|
||||
@override
|
||||
State<AccountUserProfile> createState() => _AccountUserProfileState();
|
||||
}
|
||||
|
||||
class _AccountUserProfileState extends State<AccountUserProfile> {
|
||||
late final TextEditingController nameController =
|
||||
TextEditingController(text: widget.name);
|
||||
final FocusNode focusNode = FocusNode();
|
||||
bool isEditing = false;
|
||||
bool isHovering = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
focusNode
|
||||
..addListener(_handleFocusChange)
|
||||
..onKeyEvent = _handleKeyEvent;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
nameController.dispose();
|
||||
focusNode.removeListener(_handleFocusChange);
|
||||
focusNode.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildAvatar(),
|
||||
const HSpace(16),
|
||||
Flexible(
|
||||
child: isEditing ? _buildEditingField() : _buildNameDisplay(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAvatar() {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => _showIconPickerDialog(context),
|
||||
child: FlowyHover(
|
||||
resetHoverOnRebuild: false,
|
||||
onHover: (state) => setState(() => isHovering = state),
|
||||
style: HoverStyle(
|
||||
hoverColor: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
),
|
||||
child: FlowyTooltip(
|
||||
message:
|
||||
LocaleKeys.settings_accountPage_general_changeProfilePicture.tr(),
|
||||
child: UserAvatar(
|
||||
iconUrl: widget.iconUrl,
|
||||
name: widget.name,
|
||||
size: 48,
|
||||
fontSize: 20,
|
||||
isHovering: isHovering,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNameDisplay() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FlowyText.medium(
|
||||
widget.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const HSpace(4),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => setState(() => isEditing = true),
|
||||
child: const FlowyHover(
|
||||
resetHoverOnRebuild: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(4),
|
||||
child: FlowySvg(FlowySvgs.edit_s),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEditingField() {
|
||||
return SettingsInputField(
|
||||
textController: nameController,
|
||||
value: widget.name,
|
||||
focusNode: focusNode..requestFocus(),
|
||||
onCancel: () => setState(() => isEditing = false),
|
||||
onSave: (_) => _saveChanges(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showIconPickerDialog(BuildContext context) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (dialogContext) => SimpleDialog(
|
||||
children: [
|
||||
Container(
|
||||
height: 380,
|
||||
width: 360,
|
||||
margin: const EdgeInsets.all(0),
|
||||
child: FlowyIconEmojiPicker(
|
||||
onSelectedEmoji: (r) {
|
||||
context
|
||||
.read<SettingsUserViewBloc>()
|
||||
.add(SettingsUserEvent.updateUserIcon(iconUrl: r.emoji));
|
||||
Navigator.of(dialogContext).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _handleFocusChange() {
|
||||
if (!focusNode.hasFocus && isEditing && mounted) {
|
||||
_saveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
KeyEventResult _handleKeyEvent(FocusNode node, KeyEvent event) {
|
||||
if (event is KeyDownEvent &&
|
||||
event.logicalKey == LogicalKeyboardKey.escape &&
|
||||
isEditing &&
|
||||
mounted) {
|
||||
setState(() => isEditing = false);
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
void _saveChanges() {
|
||||
widget.onSave?.call(nameController.text);
|
||||
setState(() => isEditing = false);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,15 @@
|
|||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/application/prelude.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/magic_link_sign_in_buttons.dart';
|
||||
import 'package:appflowy/workspace/application/user/settings_user_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/account/account.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_category.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_input_field.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_third_party_login.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/auth.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class SettingsAccountView extends StatefulWidget {
|
||||
|
|
@ -56,10 +46,11 @@ class _SettingsAccountViewState extends State<SettingsAccountView> {
|
|||
return SettingsBody(
|
||||
title: LocaleKeys.settings_accountPage_title.tr(),
|
||||
children: [
|
||||
// user profile
|
||||
SettingsCategory(
|
||||
title: LocaleKeys.settings_accountPage_general_title.tr(),
|
||||
children: [
|
||||
UserProfileSetting(
|
||||
AccountUserProfile(
|
||||
name: userName,
|
||||
iconUrl: state.userProfile.iconUrl,
|
||||
onSave: (newName) {
|
||||
|
|
@ -75,6 +66,7 @@ class _SettingsAccountViewState extends State<SettingsAccountView> {
|
|||
],
|
||||
),
|
||||
|
||||
// user email
|
||||
// Only show email if the user is authenticated and not using local auth
|
||||
if (isAuthEnabled &&
|
||||
state.userProfile.authenticator != AuthenticatorPB.Local) ...[
|
||||
|
|
@ -82,62 +74,15 @@ class _SettingsAccountViewState extends State<SettingsAccountView> {
|
|||
title: LocaleKeys.settings_accountPage_email_title.tr(),
|
||||
children: [
|
||||
FlowyText.regular(state.userProfile.email),
|
||||
// Enable when/if we need change email feature
|
||||
// SingleSettingAction(
|
||||
// label: state.userProfile.email,
|
||||
// buttonLabel: LocaleKeys
|
||||
// .settings_accountPage_email_actions_change
|
||||
// .tr(),
|
||||
// onPressed: () => SettingsAlertDialog(
|
||||
// title: LocaleKeys
|
||||
// .settings_accountPage_email_actions_change
|
||||
// .tr(),
|
||||
// confirmLabel: LocaleKeys.button_save.tr(),
|
||||
// confirm: () {
|
||||
// context.read<SettingsUserViewBloc>().add(
|
||||
// SettingsUserEvent.updateUserEmail(
|
||||
// _emailController.text,
|
||||
// ),
|
||||
// );
|
||||
// Navigator.of(context).pop();
|
||||
// },
|
||||
// children: [
|
||||
// SettingsInputField(
|
||||
// label: LocaleKeys.settings_accountPage_email_title
|
||||
// .tr(),
|
||||
// value: state.userProfile.email,
|
||||
// hideActions: true,
|
||||
// textController: _emailController,
|
||||
// ),
|
||||
// ],
|
||||
// ).show(context),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
/// Enable when we have change password feature and 2FA
|
||||
// const SettingsCategorySpacer(),
|
||||
// SettingsCategory(
|
||||
// title: 'Account & security',
|
||||
// children: [
|
||||
// SingleSettingAction(
|
||||
// label: '**********',
|
||||
// buttonLabel: 'Change password',
|
||||
// onPressed: () {},
|
||||
// ),
|
||||
// SingleSettingAction(
|
||||
// label: '2-step authentication',
|
||||
// buttonLabel: 'Enable 2FA',
|
||||
// onPressed: () {},
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
|
||||
// user sign in/out
|
||||
SettingsCategory(
|
||||
title: LocaleKeys.settings_accountPage_login_title.tr(),
|
||||
children: [
|
||||
SignInOutButton(
|
||||
AccountSignInOutButton(
|
||||
userProfile: state.userProfile,
|
||||
onAction:
|
||||
state.userProfile.authenticator == AuthenticatorPB.Local
|
||||
|
|
@ -149,22 +94,10 @@ class _SettingsAccountViewState extends State<SettingsAccountView> {
|
|||
],
|
||||
),
|
||||
|
||||
/// Enable when we can delete accounts
|
||||
// const SettingsCategorySpacer(),
|
||||
// SettingsSubcategory(
|
||||
// title: 'Delete account',
|
||||
// children: [
|
||||
// SingleSettingAction(
|
||||
// label:
|
||||
// 'Permanently delete your account and remove access from all teamspaces.',
|
||||
// labelMaxLines: 4,
|
||||
// onPressed: () {},
|
||||
// buttonLabel: 'Delete my account',
|
||||
// isDangerous: true,
|
||||
// fontSize: 12,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// user deletion
|
||||
if (widget.userProfile.authenticator ==
|
||||
AuthenticatorPB.AppFlowyCloud)
|
||||
const AccountDeletionButton(),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
|
@ -172,308 +105,3 @@ class _SettingsAccountViewState extends State<SettingsAccountView> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
class SignInOutButton extends StatelessWidget {
|
||||
const SignInOutButton({
|
||||
super.key,
|
||||
required this.userProfile,
|
||||
required this.onAction,
|
||||
this.signIn = true,
|
||||
});
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
final VoidCallback onAction;
|
||||
final bool signIn;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 48,
|
||||
child: PrimaryRoundedButton(
|
||||
text: signIn
|
||||
? LocaleKeys.settings_accountPage_login_loginLabel.tr()
|
||||
: LocaleKeys.settings_accountPage_login_logoutLabel.tr(),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
fontWeight: FontWeight.w600,
|
||||
radius: 12.0,
|
||||
onTap: () {
|
||||
if (signIn) {
|
||||
_showSignInDialog(context);
|
||||
} else {
|
||||
showConfirmDialog(
|
||||
context: context,
|
||||
title: LocaleKeys.settings_accountPage_login_logoutLabel.tr(),
|
||||
description: switch (userProfile.encryptionType) {
|
||||
EncryptionTypePB.Symmetric =>
|
||||
LocaleKeys.settings_menu_selfEncryptionLogoutPrompt.tr(),
|
||||
_ => LocaleKeys.settings_menu_logoutPrompt.tr(),
|
||||
},
|
||||
onConfirm: () async {
|
||||
await getIt<AuthService>().signOut();
|
||||
onAction();
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showSignInDialog(BuildContext context) async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => BlocProvider<SignInBloc>(
|
||||
create: (context) => getIt<SignInBloc>(),
|
||||
child: FlowyDialog(
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 485,
|
||||
maxWidth: 375,
|
||||
),
|
||||
child: ScaffoldMessenger(
|
||||
child: Scaffold(
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: Navigator.of(context).pop,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: Row(
|
||||
children: [
|
||||
const FlowySvg(
|
||||
FlowySvgs.arrow_back_m,
|
||||
size: Size.square(24),
|
||||
),
|
||||
const HSpace(8),
|
||||
FlowyText.semibold(
|
||||
LocaleKeys.button_back.tr(),
|
||||
fontSize: 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
GestureDetector(
|
||||
onTap: Navigator.of(context).pop,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: FlowySvg(
|
||||
FlowySvgs.m_close_m,
|
||||
size: const Size.square(20),
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.settings_accountPage_login_loginLabel
|
||||
.tr(),
|
||||
fontSize: 22,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
maxLines: null,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const VSpace(16),
|
||||
const SignInWithMagicLinkButtons(),
|
||||
if (isAuthEnabled) ...[
|
||||
const VSpace(20),
|
||||
Row(
|
||||
children: [
|
||||
const Flexible(child: Divider(thickness: 1)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
),
|
||||
child: FlowyText.regular(
|
||||
LocaleKeys.signIn_or.tr(),
|
||||
),
|
||||
),
|
||||
const Flexible(child: Divider(thickness: 1)),
|
||||
],
|
||||
),
|
||||
const VSpace(10),
|
||||
SettingThirdPartyLogin(didLogin: onAction),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
class UserProfileSetting extends StatefulWidget {
|
||||
const UserProfileSetting({
|
||||
super.key,
|
||||
required this.name,
|
||||
required this.iconUrl,
|
||||
this.onSave,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String iconUrl;
|
||||
final void Function(String)? onSave;
|
||||
|
||||
@override
|
||||
State<UserProfileSetting> createState() => _UserProfileSettingState();
|
||||
}
|
||||
|
||||
class _UserProfileSettingState extends State<UserProfileSetting> {
|
||||
late final _nameController = TextEditingController(text: widget.name);
|
||||
late final FocusNode focusNode;
|
||||
bool isEditing = false;
|
||||
bool isHovering = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
focusNode = FocusNode(
|
||||
onKeyEvent: (_, event) {
|
||||
if (event is KeyDownEvent &&
|
||||
event.logicalKey == LogicalKeyboardKey.escape &&
|
||||
isEditing &&
|
||||
mounted) {
|
||||
setState(() => isEditing = false);
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
|
||||
return KeyEventResult.ignored;
|
||||
},
|
||||
)..addListener(() {
|
||||
if (!focusNode.hasFocus && isEditing && mounted) {
|
||||
widget.onSave?.call(_nameController.text);
|
||||
setState(() => isEditing = false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => _showIconPickerDialog(context),
|
||||
child: FlowyHover(
|
||||
resetHoverOnRebuild: false,
|
||||
onHover: (state) => setState(() => isHovering = state),
|
||||
style: HoverStyle(
|
||||
hoverColor: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
),
|
||||
child: FlowyTooltip(
|
||||
message: LocaleKeys
|
||||
.settings_accountPage_general_changeProfilePicture
|
||||
.tr(),
|
||||
child: UserAvatar(
|
||||
iconUrl: widget.iconUrl,
|
||||
name: widget.name,
|
||||
size: 48,
|
||||
fontSize: 20,
|
||||
isHovering: isHovering,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const HSpace(16),
|
||||
if (!isEditing) ...[
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FlowyText.medium(
|
||||
widget.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const HSpace(4),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => setState(() => isEditing = true),
|
||||
child: const FlowyHover(
|
||||
resetHoverOnRebuild: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(4),
|
||||
child: FlowySvg(FlowySvgs.edit_s),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
Flexible(
|
||||
child: SettingsInputField(
|
||||
textController: _nameController,
|
||||
value: widget.name,
|
||||
focusNode: focusNode..requestFocus(),
|
||||
onCancel: () => setState(() => isEditing = false),
|
||||
onSave: (val) {
|
||||
widget.onSave?.call(val);
|
||||
setState(() => isEditing = false);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showIconPickerDialog(BuildContext context) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (dialogContext) => SimpleDialog(
|
||||
children: [
|
||||
Container(
|
||||
height: 380,
|
||||
width: 360,
|
||||
margin: const EdgeInsets.all(0),
|
||||
child: FlowyIconEmojiPicker(
|
||||
onSelectedEmoji: (r) {
|
||||
context
|
||||
.read<SettingsUserViewBloc>()
|
||||
.add(SettingsUserEvent.updateUserIcon(iconUrl: r.emoji));
|
||||
Navigator.of(dialogContext).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,8 +67,8 @@ class RestartButton extends StatelessWidget {
|
|||
// ],
|
||||
// );
|
||||
} else {
|
||||
return MobileSignInOrLogoutButton(
|
||||
labelText: LocaleKeys.settings_menu_restartApp.tr(),
|
||||
return MobileLogoutButton(
|
||||
text: LocaleKeys.settings_menu_restartApp.tr(),
|
||||
onPressed: onClick,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -523,3 +523,35 @@ Future<void> showCustomConfirmDialog({
|
|||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> showCancelAndDeleteDialog({
|
||||
required BuildContext context,
|
||||
required String title,
|
||||
required String description,
|
||||
required Widget Function(BuildContext) builder,
|
||||
VoidCallback? onDelete,
|
||||
String? confirmLabel,
|
||||
}) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return Dialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: 440,
|
||||
child: ConfirmPopup(
|
||||
title: title,
|
||||
description: description,
|
||||
onConfirm: () => onDelete?.call(),
|
||||
closeOnAction: false,
|
||||
confirmLabel: confirmLabel,
|
||||
confirmButtonColor: Theme.of(context).colorScheme.error,
|
||||
child: builder(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 0.6.8
|
||||
version: 0.6.9
|
||||
|
||||
environment:
|
||||
flutter: ">=3.22.0"
|
||||
|
|
|
|||
|
|
@ -2227,10 +2227,17 @@
|
|||
"deleteAccount": {
|
||||
"title": "Delete Account",
|
||||
"subtitle": "Permanently delete your account and all of your data.",
|
||||
"description": "Permanently delete your account and remove access from all teamspaces.",
|
||||
"deleteMyAccount": "Delete my account",
|
||||
"dialogTitle": "Delete account",
|
||||
"dialogContent1": "Are you sure you want to permanently delete your account?",
|
||||
"dialogContent2": "This action cannot be undone, and will remove access from all teamspaces, erasing your entire account, including private workspaces, and removing you from all shared workspaces."
|
||||
"dialogContent2": "This action cannot be undone, and will remove access from all teamspaces, erasing your entire account, including private workspaces, and removing you from all shared workspaces.",
|
||||
"confirmHint1": "Please type in your email address to confirm.",
|
||||
"confirmHint2": "I understand that this action is irreversible and will permanently delete my account and all associated data.",
|
||||
"checkToConfirmError": "You must check the box to confirm deletion",
|
||||
"failedToGetCurrentUser": "Failed to get current user email",
|
||||
"emailValidationFailed": "Your email address does not match the account email address",
|
||||
"deleteAccountSuccess": "Account deleted successfully"
|
||||
}
|
||||
},
|
||||
"workplace": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue