Compare commits
No commits in common. "3395f3dbd0e572ef9247f4da9ce174549654d93a" and "0dcfcaad56f3c86df9f61546c177b5a11b0273dd" have entirely different histories.
3395f3dbd0
...
0dcfcaad56
@ -17,7 +17,6 @@
|
|||||||
"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",
|
||||||
@ -29,7 +28,6 @@
|
|||||||
"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",
|
||||||
@ -107,15 +105,7 @@
|
|||||||
"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",
|
||||||
@ -124,12 +114,8 @@
|
|||||||
"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,7 +15,6 @@
|
|||||||
"screenAccountProfileEdit": "编辑资料",
|
"screenAccountProfileEdit": "编辑资料",
|
||||||
"screenAbuseReport": "滥用检举",
|
"screenAbuseReport": "滥用检举",
|
||||||
"screenSettings": "设置",
|
"screenSettings": "设置",
|
||||||
"screenAccountSettings": "账号设置",
|
|
||||||
"screenNews": "新闻",
|
"screenNews": "新闻",
|
||||||
"screenAlbum": "相册",
|
"screenAlbum": "相册",
|
||||||
"screenChat": "聊天",
|
"screenChat": "聊天",
|
||||||
@ -90,15 +89,7 @@
|
|||||||
"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": "退出登录",
|
||||||
@ -107,12 +98,8 @@
|
|||||||
"accountLogoutConfirm": "您需要重新输入账号密码,甚至可能需要多步验证来再次登陆。",
|
"accountLogoutConfirm": "您需要重新输入账号密码,甚至可能需要多步验证来再次登陆。",
|
||||||
"accountPublishers": "你的发布者",
|
"accountPublishers": "你的发布者",
|
||||||
"accountPublishersSubtitle": "管理你的公共形象。",
|
"accountPublishersSubtitle": "管理你的公共形象。",
|
||||||
"accountSettings": "帐号设置",
|
|
||||||
"accountSettingsSubtitle": "管理你的帐号并让它更好的服务你。",
|
|
||||||
"accountProfileEdit": "编辑资料",
|
"accountProfileEdit": "编辑资料",
|
||||||
"accountProfileEditSubtitle": "使你的 Solarpass 账户更像你。",
|
"accountProfileEditSubtitle": "使你的 Solarpass 账户更像你。",
|
||||||
"factorSettings": "验证因子",
|
|
||||||
"factorSettingsSubtitle": "管理你的登陆验证方式。",
|
|
||||||
"accountProfileEditApplied": "个人资料修改已被应用。",
|
"accountProfileEditApplied": "个人资料修改已被应用。",
|
||||||
"publishersNew": "新发布者",
|
"publishersNew": "新发布者",
|
||||||
"publisherNewSubtitle": "创建一个新的公共身份。",
|
"publisherNewSubtitle": "创建一个新的公共身份。",
|
||||||
|
@ -3,8 +3,6 @@ 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';
|
||||||
@ -98,47 +96,11 @@ final _appRoutes = [
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
GoRoute(path: '/account', name: 'account', builder: (context, state) => const AccountScreen(), routes: [
|
GoRoute(
|
||||||
GoRoute(
|
path: '/account',
|
||||||
path: '/settings',
|
name: 'account',
|
||||||
name: 'accountSettings',
|
builder: (context, state) => const AccountScreen(),
|
||||||
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',
|
||||||
@ -199,15 +161,20 @@ final _appRoutes = [
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
GoRoute(path: '/news', name: 'news', builder: (context, state) => const NewsScreen(), routes: [
|
GoRoute(
|
||||||
GoRoute(
|
path: '/news',
|
||||||
path: '/:hash',
|
name: 'news',
|
||||||
name: 'newsDetail',
|
builder: (context, state) => const NewsScreen(),
|
||||||
builder: (context, state) => NewsDetailScreen(
|
routes: [
|
||||||
hash: state.pathParameters['hash']!,
|
GoRoute(
|
||||||
|
path: '/:hash',
|
||||||
|
name: 'newsDetail',
|
||||||
|
builder: (context, state) => NewsDetailScreen(
|
||||||
|
hash: state.pathParameters['hash']!,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
]
|
||||||
]),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/album',
|
path: '/album',
|
||||||
name: 'album',
|
name: 'album',
|
||||||
@ -238,6 +205,35 @@ 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,5 +1,3 @@
|
|||||||
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';
|
||||||
@ -15,7 +13,6 @@ 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});
|
||||||
@ -23,39 +20,11 @@ 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),
|
||||||
@ -114,6 +83,16 @@ 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(),
|
||||||
@ -134,26 +113,6 @@ 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(),
|
||||||
@ -175,6 +134,33 @@ 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,201 +0,0 @@
|
|||||||
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,
|
||||||
@Default("") String avatar,
|
required String avatar,
|
||||||
@Default("") String banner,
|
required 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,
|
||||||
this.avatar = "",
|
required this.avatar,
|
||||||
this.banner = "",
|
required this.banner,
|
||||||
required this.description,
|
required this.description,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.nick,
|
required this.nick,
|
||||||
@ -410,10 +410,8 @@ 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;
|
||||||
@ -542,8 +540,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,
|
||||||
final String avatar,
|
required final String avatar,
|
||||||
final String banner,
|
required 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