Compare commits
2 Commits
0dcfcaad56
...
3395f3dbd0
Author | SHA1 | Date | |
---|---|---|---|
3395f3dbd0 | |||
d258ba776e |
@ -17,6 +17,7 @@
|
|||||||
"screenAccountProfileEdit": "Edit Profile",
|
"screenAccountProfileEdit": "Edit Profile",
|
||||||
"screenAbuseReport": "Abuse Reports",
|
"screenAbuseReport": "Abuse Reports",
|
||||||
"screenSettings": "Settings",
|
"screenSettings": "Settings",
|
||||||
|
"screenAccountSettings": "Account Settings",
|
||||||
"screenNews": "News",
|
"screenNews": "News",
|
||||||
"screenAlbum": "Album",
|
"screenAlbum": "Album",
|
||||||
"screenChat": "Chat",
|
"screenChat": "Chat",
|
||||||
@ -28,6 +29,7 @@
|
|||||||
"screenNotification": "Notification",
|
"screenNotification": "Notification",
|
||||||
"screenPostSearch": "Search Posts",
|
"screenPostSearch": "Search Posts",
|
||||||
"screenFriend": "Friends",
|
"screenFriend": "Friends",
|
||||||
|
"screenFactorSettings": "Auth Factors",
|
||||||
"dialogOkay": "Okay",
|
"dialogOkay": "Okay",
|
||||||
"dialogCancel": "Cancel",
|
"dialogCancel": "Cancel",
|
||||||
"dialogConfirm": "Confirm",
|
"dialogConfirm": "Confirm",
|
||||||
@ -105,7 +107,15 @@
|
|||||||
"loginEnterPassword": "Enter the code",
|
"loginEnterPassword": "Enter the code",
|
||||||
"loginSuccess": "Logged in as {}",
|
"loginSuccess": "Logged in as {}",
|
||||||
"authFactorPassword": "Password",
|
"authFactorPassword": "Password",
|
||||||
|
"authFactorPasswordDescription": "The password you set when you registered.",
|
||||||
"authFactorEmail": "Email verification code",
|
"authFactorEmail": "Email verification code",
|
||||||
|
"authFactorEmailDescription": "An one-time code sent to the email address you set when you registered.",
|
||||||
|
"authFactorTOTP": "Time-based OTP",
|
||||||
|
"authFactorTOTPDescription": "A one-time code generated by a TOTP authenticator such as Google Authenticator or Authy.",
|
||||||
|
"authFactorInAppNotify": "In-app notification",
|
||||||
|
"authFactorInAppNotifyDescription": "A one-time code sent via in-app notification.",
|
||||||
|
"authFactorAdd": "Add a factor",
|
||||||
|
"authFactorAddSubtitle": "Provide another way to login your account.",
|
||||||
"accountIntroTitle": "Hello there!",
|
"accountIntroTitle": "Hello there!",
|
||||||
"accountIntroSubtitle": "Pick an option below to get started.",
|
"accountIntroSubtitle": "Pick an option below to get started.",
|
||||||
"accountLogout": "Logout",
|
"accountLogout": "Logout",
|
||||||
@ -114,8 +124,12 @@
|
|||||||
"accountLogoutConfirm": "You will need to re-enter your account password, even if you have already done so. This is required to login again.",
|
"accountLogoutConfirm": "You will need to re-enter your account password, even if you have already done so. This is required to login again.",
|
||||||
"accountPublishers": "Your publishers",
|
"accountPublishers": "Your publishers",
|
||||||
"accountPublishersSubtitle": "Manage your publish identities.",
|
"accountPublishersSubtitle": "Manage your publish identities.",
|
||||||
|
"accountSettings": "Account Settings",
|
||||||
|
"accountSettingsSubtitle": "Manage your account and make it yours.",
|
||||||
"accountProfileEdit": "Edit your profile",
|
"accountProfileEdit": "Edit your profile",
|
||||||
"accountProfileEditSubtitle": "Make your Solarpass account more looks like you.",
|
"accountProfileEditSubtitle": "Make your Solarpass account more looks like you.",
|
||||||
|
"factorSettings": "Auth Factors",
|
||||||
|
"factorSettingsSubtitle": "Manage your authentication factors.",
|
||||||
"accountProfileEditApplied": "Profile modification applied.",
|
"accountProfileEditApplied": "Profile modification applied.",
|
||||||
"publishersNew": "New Publisher",
|
"publishersNew": "New Publisher",
|
||||||
"publisherNewSubtitle": "Create a new publisher identity.",
|
"publisherNewSubtitle": "Create a new publisher identity.",
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
"screenAccountProfileEdit": "编辑资料",
|
"screenAccountProfileEdit": "编辑资料",
|
||||||
"screenAbuseReport": "滥用检举",
|
"screenAbuseReport": "滥用检举",
|
||||||
"screenSettings": "设置",
|
"screenSettings": "设置",
|
||||||
|
"screenAccountSettings": "账号设置",
|
||||||
"screenNews": "新闻",
|
"screenNews": "新闻",
|
||||||
"screenAlbum": "相册",
|
"screenAlbum": "相册",
|
||||||
"screenChat": "聊天",
|
"screenChat": "聊天",
|
||||||
@ -89,7 +90,15 @@
|
|||||||
"loginEnterPassword": "验证代码",
|
"loginEnterPassword": "验证代码",
|
||||||
"loginSuccess": "登录为 {}",
|
"loginSuccess": "登录为 {}",
|
||||||
"authFactorPassword": "密码",
|
"authFactorPassword": "密码",
|
||||||
|
"authFactorPasswordDescription": "注册时选择设置的密码。",
|
||||||
"authFactorEmail": "电邮一次性验证码",
|
"authFactorEmail": "电邮一次性验证码",
|
||||||
|
"authFactorEmailDescription": "由我们生成并发送到绑定的的电子邮箱的一次性验证码。",
|
||||||
|
"authFactorTOTP": "时序验证码",
|
||||||
|
"authFactorTOTPDescription": "使用 Google Authenticator 或 Authy 等验证器生成的一次性验证码。",
|
||||||
|
"authFactorInAppNotify": "应用内通知验证码",
|
||||||
|
"authFactorInAppNotifyDescription": "通过站内通知推送的一次性验证码。",
|
||||||
|
"authFactorAdd": "添加新验证因子",
|
||||||
|
"authFactorAddSubtitle": "给你的帐户登陆时提供另一个方案。",
|
||||||
"accountIntroTitle": "喜欢您来!",
|
"accountIntroTitle": "喜欢您来!",
|
||||||
"accountIntroSubtitle": "登陆以探索更广大的世界。",
|
"accountIntroSubtitle": "登陆以探索更广大的世界。",
|
||||||
"accountLogout": "退出登录",
|
"accountLogout": "退出登录",
|
||||||
@ -98,8 +107,12 @@
|
|||||||
"accountLogoutConfirm": "您需要重新输入账号密码,甚至可能需要多步验证来再次登陆。",
|
"accountLogoutConfirm": "您需要重新输入账号密码,甚至可能需要多步验证来再次登陆。",
|
||||||
"accountPublishers": "你的发布者",
|
"accountPublishers": "你的发布者",
|
||||||
"accountPublishersSubtitle": "管理你的公共形象。",
|
"accountPublishersSubtitle": "管理你的公共形象。",
|
||||||
|
"accountSettings": "帐号设置",
|
||||||
|
"accountSettingsSubtitle": "管理你的帐号并让它更好的服务你。",
|
||||||
"accountProfileEdit": "编辑资料",
|
"accountProfileEdit": "编辑资料",
|
||||||
"accountProfileEditSubtitle": "使你的 Solarpass 账户更像你。",
|
"accountProfileEditSubtitle": "使你的 Solarpass 账户更像你。",
|
||||||
|
"factorSettings": "验证因子",
|
||||||
|
"factorSettingsSubtitle": "管理你的登陆验证方式。",
|
||||||
"accountProfileEditApplied": "个人资料修改已被应用。",
|
"accountProfileEditApplied": "个人资料修改已被应用。",
|
||||||
"publishersNew": "新发布者",
|
"publishersNew": "新发布者",
|
||||||
"publisherNewSubtitle": "创建一个新的公共身份。",
|
"publisherNewSubtitle": "创建一个新的公共身份。",
|
||||||
|
@ -3,6 +3,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:surface/screens/abuse_report.dart';
|
import 'package:surface/screens/abuse_report.dart';
|
||||||
import 'package:surface/screens/account.dart';
|
import 'package:surface/screens/account.dart';
|
||||||
|
import 'package:surface/screens/account/account_settings.dart';
|
||||||
|
import 'package:surface/screens/account/factor_settings.dart';
|
||||||
import 'package:surface/screens/account/profile_page.dart';
|
import 'package:surface/screens/account/profile_page.dart';
|
||||||
import 'package:surface/screens/account/profile_edit.dart';
|
import 'package:surface/screens/account/profile_edit.dart';
|
||||||
import 'package:surface/screens/account/publishers/publisher_edit.dart';
|
import 'package:surface/screens/account/publishers/publisher_edit.dart';
|
||||||
@ -96,11 +98,47 @@ final _appRoutes = [
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(path: '/account', name: 'account', builder: (context, state) => const AccountScreen(), routes: [
|
||||||
path: '/account',
|
GoRoute(
|
||||||
name: 'account',
|
path: '/settings',
|
||||||
builder: (context, state) => const AccountScreen(),
|
name: 'accountSettings',
|
||||||
),
|
builder: (context, state) => AccountSettingsScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/settings/factors',
|
||||||
|
name: 'factorSettings',
|
||||||
|
builder: (context, state) => FactorSettingsScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/profile/edit',
|
||||||
|
name: 'accountProfileEdit',
|
||||||
|
builder: (context, state) => ProfileEditScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/publishers',
|
||||||
|
name: 'accountPublishers',
|
||||||
|
builder: (context, state) => PublisherScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/publishers/new',
|
||||||
|
name: 'accountPublisherNew',
|
||||||
|
builder: (context, state) => AccountPublisherNewScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/publishers/edit/:name',
|
||||||
|
name: 'accountPublisherEdit',
|
||||||
|
builder: (context, state) => AccountPublisherEditScreen(
|
||||||
|
name: state.pathParameters['name']!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/:name',
|
||||||
|
name: 'accountProfilePage',
|
||||||
|
pageBuilder: (context, state) => NoTransitionPage(
|
||||||
|
child: UserScreen(name: state.pathParameters['name']!),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/chat',
|
path: '/chat',
|
||||||
name: 'chat',
|
name: 'chat',
|
||||||
@ -161,20 +199,15 @@ final _appRoutes = [
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(path: '/news', name: 'news', builder: (context, state) => const NewsScreen(), routes: [
|
||||||
path: '/news',
|
GoRoute(
|
||||||
name: 'news',
|
path: '/:hash',
|
||||||
builder: (context, state) => const NewsScreen(),
|
name: 'newsDetail',
|
||||||
routes: [
|
builder: (context, state) => NewsDetailScreen(
|
||||||
GoRoute(
|
hash: state.pathParameters['hash']!,
|
||||||
path: '/:hash',
|
|
||||||
name: 'newsDetail',
|
|
||||||
builder: (context, state) => NewsDetailScreen(
|
|
||||||
hash: state.pathParameters['hash']!,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
]
|
),
|
||||||
),
|
]),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/album',
|
path: '/album',
|
||||||
name: 'album',
|
name: 'album',
|
||||||
@ -205,35 +238,6 @@ final _appRoutes = [
|
|||||||
name: 'abuseReport',
|
name: 'abuseReport',
|
||||||
builder: (context, state) => AbuseReportScreen(),
|
builder: (context, state) => AbuseReportScreen(),
|
||||||
),
|
),
|
||||||
GoRoute(
|
|
||||||
path: '/account/profile/edit',
|
|
||||||
name: 'accountProfileEdit',
|
|
||||||
builder: (context, state) => ProfileEditScreen(),
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
path: '/account/publishers',
|
|
||||||
name: 'accountPublishers',
|
|
||||||
builder: (context, state) => PublisherScreen(),
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
path: '/account/publishers/new',
|
|
||||||
name: 'accountPublisherNew',
|
|
||||||
builder: (context, state) => AccountPublisherNewScreen(),
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
path: '/account/publishers/edit/:name',
|
|
||||||
name: 'accountPublisherEdit',
|
|
||||||
builder: (context, state) => AccountPublisherEditScreen(
|
|
||||||
name: state.pathParameters['name']!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
path: '/account/:name',
|
|
||||||
name: 'accountProfilePage',
|
|
||||||
pageBuilder: (context, state) => NoTransitionPage(
|
|
||||||
child: UserScreen(name: state.pathParameters['name']!),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
name: 'settings',
|
name: 'settings',
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
@ -13,6 +15,7 @@ import 'package:surface/widgets/account/account_image.dart';
|
|||||||
import 'package:surface/widgets/app_bar_leading.dart';
|
import 'package:surface/widgets/app_bar_leading.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
|
|
||||||
class AccountScreen extends StatelessWidget {
|
class AccountScreen extends StatelessWidget {
|
||||||
const AccountScreen({super.key});
|
const AccountScreen({super.key});
|
||||||
@ -20,11 +23,39 @@ class AccountScreen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final ua = context.watch<UserProvider>();
|
final ua = context.watch<UserProvider>();
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: AutoAppBarLeading(),
|
leading: AutoAppBarLeading(),
|
||||||
title: Text("screenAccount").tr(),
|
title: Text("screenAccount").tr(),
|
||||||
|
flexibleSpace: ua.user != null && ua.user!.banner.isNotEmpty
|
||||||
|
? Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
AutoResizeUniversalImage(sn.getAttachmentUrl(ua.user!.banner), fit: BoxFit.cover),
|
||||||
|
Positioned(
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: 56 + MediaQuery.of(context).padding.top,
|
||||||
|
child: ClipRect(
|
||||||
|
child: BackdropFilter(
|
||||||
|
filter: ImageFilter.blur(
|
||||||
|
sigmaX: 10,
|
||||||
|
sigmaY: 10,
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.black.withOpacity(
|
||||||
|
clampDouble(10 * 0.1, 0, 0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: null,
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.settings, fill: 1),
|
icon: const Icon(Symbols.settings, fill: 1),
|
||||||
@ -83,16 +114,6 @@ class _AuthorizedAccountScreen extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}).padding(all: 20),
|
}).padding(all: 20),
|
||||||
).padding(horizontal: 8, top: 16, bottom: 4),
|
).padding(horizontal: 8, top: 16, bottom: 4),
|
||||||
ListTile(
|
|
||||||
title: Text('accountProfileEdit').tr(),
|
|
||||||
subtitle: Text('accountProfileEditSubtitle').tr(),
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
leading: const Icon(Symbols.contact_page),
|
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
|
||||||
onTap: () {
|
|
||||||
GoRouter.of(context).pushNamed('accountProfileEdit');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('accountPublishers').tr(),
|
title: Text('accountPublishers').tr(),
|
||||||
subtitle: Text('accountPublishersSubtitle').tr(),
|
subtitle: Text('accountPublishersSubtitle').tr(),
|
||||||
@ -113,6 +134,26 @@ class _AuthorizedAccountScreen extends StatelessWidget {
|
|||||||
GoRouter.of(context).pushNamed('abuseReport');
|
GoRouter.of(context).pushNamed('abuseReport');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('factorSettings').tr(),
|
||||||
|
subtitle: Text('factorSettingsSubtitle').tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Symbols.lock),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed('factorSettings');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('accountSettings').tr(),
|
||||||
|
subtitle: Text('accountSettingsSubtitle').tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Symbols.manage_accounts),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed('accountSettings');
|
||||||
|
},
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('accountLogout').tr(),
|
title: Text('accountLogout').tr(),
|
||||||
subtitle: Text('accountLogoutSubtitle').tr(),
|
subtitle: Text('accountLogoutSubtitle').tr(),
|
||||||
@ -134,33 +175,6 @@ class _AuthorizedAccountScreen extends StatelessWidget {
|
|||||||
await Hive.initFlutter();
|
await Hive.initFlutter();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
|
||||||
title: Text('accountDeletion'.tr()),
|
|
||||||
subtitle: Text('accountDeletionActionDescription'.tr()),
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
leading: const Icon(Symbols.person_cancel),
|
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
|
||||||
onTap: () {
|
|
||||||
context
|
|
||||||
.showConfirmDialog(
|
|
||||||
'accountDeletion'.tr(),
|
|
||||||
'accountDeletionDescription'.tr(),
|
|
||||||
)
|
|
||||||
.then((value) {
|
|
||||||
if (!value || !context.mounted) return;
|
|
||||||
final sn = context.read<SnNetworkProvider>();
|
|
||||||
sn.client.post('/cgi/id/users/me/deletion').then((value) {
|
|
||||||
if (context.mounted) {
|
|
||||||
context.showSnackbar('accountDeletionSubmitted'.tr());
|
|
||||||
}
|
|
||||||
}).catchError((err) {
|
|
||||||
if (context.mounted) {
|
|
||||||
context.showErrorDialog(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
66
lib/screens/account/account_settings.dart
Normal file
66
lib/screens/account/account_settings.dart
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
|
||||||
|
class AccountSettingsScreen extends StatelessWidget {
|
||||||
|
const AccountSettingsScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: PageBackButton(),
|
||||||
|
title: Text('screenAccountSettings').tr(),
|
||||||
|
),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
title: Text('accountProfileEdit').tr(),
|
||||||
|
subtitle: Text('accountProfileEditSubtitle').tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Symbols.contact_page),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed('accountProfileEdit');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('accountDeletion'.tr()),
|
||||||
|
subtitle: Text('accountDeletionActionDescription'.tr()),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Symbols.person_cancel),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
context
|
||||||
|
.showConfirmDialog(
|
||||||
|
'accountDeletion'.tr(),
|
||||||
|
'accountDeletionDescription'.tr(),
|
||||||
|
)
|
||||||
|
.then((value) {
|
||||||
|
if (!value || !context.mounted) return;
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
sn.client.post('/cgi/id/users/me/deletion').then((value) {
|
||||||
|
if (context.mounted) {
|
||||||
|
context.showSnackbar('accountDeletionSubmitted'.tr());
|
||||||
|
}
|
||||||
|
}).catchError((err) {
|
||||||
|
if (context.mounted) {
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
201
lib/screens/account/factor_settings.dart
Normal file
201
lib/screens/account/factor_settings.dart
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/types/auth.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
|
||||||
|
final Map<int, (String, String, IconData)> _kFactorTypes = {
|
||||||
|
0: ('authFactorPassword', 'authFactorPasswordDescription', Symbols.password),
|
||||||
|
1: ('authFactorEmail', 'authFactorEmailDescription', Symbols.email),
|
||||||
|
2: ('authFactorTOTP', 'authFactorTOTPDescription', Symbols.timer),
|
||||||
|
3: ('authFactorInAppNotify', 'authFactorInAppNotifyDescription', Symbols.notifications_active),
|
||||||
|
};
|
||||||
|
|
||||||
|
class FactorSettingsScreen extends StatefulWidget {
|
||||||
|
const FactorSettingsScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<FactorSettingsScreen> createState() => _FactorSettingsScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FactorSettingsScreenState extends State<FactorSettingsScreen> {
|
||||||
|
List<SnAuthFactor>? _factors;
|
||||||
|
|
||||||
|
Future<void> _fetchFactors() async {
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/id/users/me/factors');
|
||||||
|
_factors = List<SnAuthFactor>.from(
|
||||||
|
resp.data?.map((e) => SnAuthFactor.fromJson(e as Map<String, dynamic>)).toList() ?? [],
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_fetchFactors();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: PageBackButton(),
|
||||||
|
title: Text('screenFactorSettings').tr(),
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
LoadingIndicator(
|
||||||
|
isActive: _factors == null,
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('authFactorAdd').tr(),
|
||||||
|
subtitle: Text('authFactorAddSubtitle').tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Symbols.add),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => _FactorNewDialog(
|
||||||
|
currentlyHave: _factors!,
|
||||||
|
),
|
||||||
|
).then((val) {
|
||||||
|
if (val == true) _fetchFactors();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
Expanded(
|
||||||
|
child: MediaQuery.removePadding(
|
||||||
|
context: context,
|
||||||
|
removeTop: true,
|
||||||
|
child: RefreshIndicator(
|
||||||
|
onRefresh: _fetchFactors,
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: _factors?.length ?? 0,
|
||||||
|
itemBuilder: (context, idx) {
|
||||||
|
final ele = _factors![idx];
|
||||||
|
return ListTile(
|
||||||
|
title: Text(_kFactorTypes[ele.type]!.$1).tr(),
|
||||||
|
subtitle: Text(_kFactorTypes[ele.type]!.$2).tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: Icon(_kFactorTypes[ele.type]!.$3),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FactorNewDialog extends StatefulWidget {
|
||||||
|
final List<SnAuthFactor> currentlyHave;
|
||||||
|
|
||||||
|
const _FactorNewDialog({required this.currentlyHave});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_FactorNewDialog> createState() => _FactorNewDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FactorNewDialogState extends State<_FactorNewDialog> {
|
||||||
|
int? _factorType;
|
||||||
|
bool _isBusy = false;
|
||||||
|
|
||||||
|
Future<void> _submit() async {
|
||||||
|
try {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.post('/cgi/id/users/me/factors', data: {
|
||||||
|
'type': _factorType,
|
||||||
|
});
|
||||||
|
// TODO show qrcode when creating totp factor
|
||||||
|
if (!mounted) return;
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text('authFactorAdd').tr(),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
DropdownButtonHideUnderline(
|
||||||
|
child: DropdownButton2<int>(
|
||||||
|
hint: Text(
|
||||||
|
'Select Item',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
value: _factorType,
|
||||||
|
items: _kFactorTypes.entries.map(
|
||||||
|
(ele) {
|
||||||
|
final contains = widget.currentlyHave.map((ele) => ele.type).contains(ele.key);
|
||||||
|
return DropdownMenuItem<int>(
|
||||||
|
enabled: !contains,
|
||||||
|
value: ele.key,
|
||||||
|
child: Text(
|
||||||
|
ele.value.$1.tr(),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
).opacity(contains ? 0.75 : 1),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
).toList(),
|
||||||
|
onChanged: (val) => setState(() {
|
||||||
|
_factorType = val;
|
||||||
|
}),
|
||||||
|
buttonStyleData: ButtonStyleData(
|
||||||
|
height: 50,
|
||||||
|
padding: const EdgeInsets.only(left: 14, right: 14),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(14),
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: _isBusy ? null : () => Navigator.of(context).pop(),
|
||||||
|
child: Text('dialogCancel').tr(),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: _isBusy ? null : () => _submit(),
|
||||||
|
child: Text('dialogConfirm').tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -15,8 +15,8 @@ class SnAccount with _$SnAccount {
|
|||||||
required DateTime? deletedAt,
|
required DateTime? deletedAt,
|
||||||
required DateTime? confirmedAt,
|
required DateTime? confirmedAt,
|
||||||
required List<SnAccountContact>? contacts,
|
required List<SnAccountContact>? contacts,
|
||||||
required String avatar,
|
@Default("") String avatar,
|
||||||
required String banner,
|
@Default("") String banner,
|
||||||
required String description,
|
required String description,
|
||||||
required String name,
|
required String name,
|
||||||
required String nick,
|
required String nick,
|
||||||
|
@ -367,8 +367,8 @@ class _$SnAccountImpl extends _SnAccount {
|
|||||||
required this.deletedAt,
|
required this.deletedAt,
|
||||||
required this.confirmedAt,
|
required this.confirmedAt,
|
||||||
required final List<SnAccountContact>? contacts,
|
required final List<SnAccountContact>? contacts,
|
||||||
required this.avatar,
|
this.avatar = "",
|
||||||
required this.banner,
|
this.banner = "",
|
||||||
required this.description,
|
required this.description,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.nick,
|
required this.nick,
|
||||||
@ -410,8 +410,10 @@ class _$SnAccountImpl extends _SnAccount {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@JsonKey()
|
||||||
final String avatar;
|
final String avatar;
|
||||||
@override
|
@override
|
||||||
|
@JsonKey()
|
||||||
final String banner;
|
final String banner;
|
||||||
@override
|
@override
|
||||||
final String description;
|
final String description;
|
||||||
@ -540,8 +542,8 @@ abstract class _SnAccount extends SnAccount {
|
|||||||
required final DateTime? deletedAt,
|
required final DateTime? deletedAt,
|
||||||
required final DateTime? confirmedAt,
|
required final DateTime? confirmedAt,
|
||||||
required final List<SnAccountContact>? contacts,
|
required final List<SnAccountContact>? contacts,
|
||||||
required final String avatar,
|
final String avatar,
|
||||||
required final String banner,
|
final String banner,
|
||||||
required final String description,
|
required final String description,
|
||||||
required final String name,
|
required final String name,
|
||||||
required final String nick,
|
required final String nick,
|
||||||
|
@ -20,8 +20,8 @@ _$SnAccountImpl _$$SnAccountImplFromJson(Map<String, dynamic> json) =>
|
|||||||
contacts: (json['contacts'] as List<dynamic>?)
|
contacts: (json['contacts'] as List<dynamic>?)
|
||||||
?.map((e) => SnAccountContact.fromJson(e as Map<String, dynamic>))
|
?.map((e) => SnAccountContact.fromJson(e as Map<String, dynamic>))
|
||||||
.toList(),
|
.toList(),
|
||||||
avatar: json['avatar'] as String,
|
avatar: json['avatar'] as String? ?? "",
|
||||||
banner: json['banner'] as String,
|
banner: json['banner'] as String? ?? "",
|
||||||
description: json['description'] as String,
|
description: json['description'] as String,
|
||||||
name: json['name'] as String,
|
name: json['name'] as String,
|
||||||
nick: json['nick'] as String,
|
nick: json['nick'] as String,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user