💄 Optimized account creation

This commit is contained in:
LittleSheep 2025-04-30 00:22:12 +08:00
parent 627b726bbc
commit a4e27e57a3
6 changed files with 134 additions and 43 deletions

View File

@ -27,6 +27,7 @@
"logout": "Logout",
"updateYourProfile": "Edit Profile",
"accountBasicInfo": "Basic Info",
"accountProfile": "Your Profile",
"saveChanges": "Save Changes",
"publishers": "Publishers",
"managedPublisher": "Managed Publishers",
@ -41,5 +42,9 @@
"somethingWentWrong": "Something went wrong...",
"deletePost": "Delete Post",
"deletePostHint": "Are you sure to delete this post?",
"copyLink": "Copy Link"
"copyLink": "Copy Link",
"postCreateAccountTitle": "Thanks for joining!",
"postCreateAccountNext": "What's next?",
"postCreateAccountNext1": "Go to your email inbox and receive the account activation email.",
"postCreateAccountNext2": "Log in to your account and start exploring the Solar Network!"
}

View File

@ -67,17 +67,22 @@ final apiClientProvider = Provider<Dio>((ref) {
RequestOptions options,
RequestInterceptorHandler handler,
) async {
final atk = await getFreshAtk(
ref.watch(tokenPairProvider),
ref.watch(serverUrlProvider),
onRefreshed: (atk, rtk) {
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
ref.invalidate(tokenPairProvider);
},
);
if (atk != null) {
options.headers['Authorization'] = 'Bearer $atk';
try {
final atk = await getFreshAtk(
ref.watch(tokenPairProvider),
ref.watch(serverUrlProvider),
onRefreshed: (atk, rtk) {
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
ref.invalidate(tokenPairProvider);
},
);
if (atk != null) {
options.headers['Authorization'] = 'Bearer $atk';
}
} catch (err) {
// ignore
}
final userAgent = ref.watch(userAgentProvider);
if (userAgent.value != null) {
options.headers['User-Agent'] = userAgent.value;

View File

@ -10,6 +10,7 @@ import 'package:island/route.gr.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:url_launcher/url_launcher_string.dart';
@ -23,21 +24,21 @@ class CreateAccountScreen extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final formKey = useMemoized(GlobalKey<FormState>.new, const []);
final email = useState('');
final username = useState('');
final nickname = useState('');
final password = useState('');
final emailController = useTextEditingController();
final usernameController = useTextEditingController();
final nicknameController = useTextEditingController();
final passwordController = useTextEditingController();
void showPostCreateModal() {
showCupertinoModalBottomSheet(
context: context,
builder: (context) => _PostCreateModal(),
);
}
void performAction() async {
if (!formKey.currentState!.validate()) return;
if (email.value.isEmpty ||
username.value.isEmpty ||
nickname.value.isEmpty ||
password.value.isEmpty) {
return;
}
final captchaTk = await Navigator.of(
context,
).push(MaterialPageRoute(builder: (context) => CaptchaScreen()));
@ -48,19 +49,19 @@ class CreateAccountScreen extends HookConsumerWidget {
try {
final client = ref.watch(apiClientProvider);
await client.post(
'/users',
'/accounts',
data: {
'name': username.value,
'nick': nickname.value,
'email': email.value,
'password': password.value,
'name': usernameController.text,
'nick': nicknameController.text,
'email': emailController.text,
'password': passwordController.text,
'language': EasyLocalization.of(context)!.currentLocale.toString(),
'captcha_token': captchaTk,
},
);
if (!context.mounted) return;
context.router.replace(CreateAccountRoute());
showPostCreateModal();
} catch (err) {
showErrorAlert(err);
}
@ -99,7 +100,7 @@ class CreateAccountScreen extends HookConsumerWidget {
child: Column(
children: [
TextFormField(
initialValue: username.value,
controller: usernameController,
validator: (value) {
if (value == null || value.isEmpty) {
return 'fieldCannotBeEmpty'.tr();
@ -108,12 +109,12 @@ class CreateAccountScreen extends HookConsumerWidget {
},
autocorrect: false,
enableSuggestions: false,
onSaved: (val) => username.value = val ?? '',
autofillHints: const [AutofillHints.username],
decoration: InputDecoration(
isDense: true,
border: const UnderlineInputBorder(),
labelText: 'username'.tr(),
helperText: 'usernameCannotChangeHint'.tr(),
),
onTapOutside:
(_) =>
@ -122,7 +123,7 @@ class CreateAccountScreen extends HookConsumerWidget {
),
const Gap(12),
TextFormField(
initialValue: nickname.value,
controller: nicknameController,
validator: (value) {
if (value == null || value.isEmpty) {
return 'fieldCannotBeEmpty'.tr();
@ -130,8 +131,6 @@ class CreateAccountScreen extends HookConsumerWidget {
return null;
},
autocorrect: false,
enableSuggestions: false,
onSaved: (val) => nickname.value = val ?? '',
autofillHints: const [AutofillHints.nickname],
decoration: InputDecoration(
isDense: true,
@ -145,7 +144,7 @@ class CreateAccountScreen extends HookConsumerWidget {
),
const Gap(12),
TextFormField(
initialValue: email.value,
controller: emailController,
validator: (value) {
if (value == null || value.isEmpty) {
return 'fieldCannotBeEmpty'.tr();
@ -157,7 +156,6 @@ class CreateAccountScreen extends HookConsumerWidget {
},
autocorrect: false,
enableSuggestions: false,
onSaved: (val) => email.value = val ?? '',
autofillHints: const [AutofillHints.email],
decoration: InputDecoration(
isDense: true,
@ -171,7 +169,7 @@ class CreateAccountScreen extends HookConsumerWidget {
),
const Gap(12),
TextFormField(
initialValue: password.value,
controller: passwordController,
validator: (value) {
if (value == null || value.isEmpty) {
return 'fieldCannotBeEmpty'.tr();
@ -181,7 +179,6 @@ class CreateAccountScreen extends HookConsumerWidget {
obscureText: true,
autocorrect: false,
enableSuggestions: false,
onSaved: (val) => password.value = val ?? '',
autofillHints: const [AutofillHints.password],
decoration: InputDecoration(
isDense: true,
@ -245,9 +242,7 @@ class CreateAccountScreen extends HookConsumerWidget {
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () {
if (formKey.currentState?.validate() ?? false) {
performAction();
}
performAction();
},
child: Row(
mainAxisSize: MainAxisSize.min,
@ -266,3 +261,57 @@ class CreateAccountScreen extends HookConsumerWidget {
);
}
}
class _PostCreateModal extends HookConsumerWidget {
const _PostCreateModal();
@override
Widget build(BuildContext context, WidgetRef ref) {
return Center(
child: Material(
color: Colors.transparent,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 280),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('🎉').fontSize(32),
Text(
'postCreateAccountTitle'.tr(),
textAlign: TextAlign.center,
).fontSize(17),
const Gap(18),
Text('postCreateAccountNext').tr().fontSize(19).bold(),
const Gap(4),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 6,
children: [
Text('\u2022'),
Expanded(child: Text('postCreateAccountNext1').tr()),
],
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 6,
children: [
Text('\u2022'),
Expanded(child: Text('postCreateAccountNext2').tr()),
],
),
const Gap(6),
TextButton(
onPressed: () {
Navigator.pop(context);
context.router.replace(LoginRoute());
},
child: Text('login'.tr()),
),
],
),
),
),
);
}
}

View File

@ -1,10 +1,33 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_platform_alert/flutter_platform_alert.dart';
String _parseRemoteError(DioException err) {
if (err.response?.data is String) return err.response?.data;
if (err.response?.data?['errors'] != null) {
final errors = err.response?.data['errors'] as Map<String, dynamic>;
return errors.values
.map(
(ele) =>
(ele as List<dynamic>).map((ele) => ele.toString()).join('\n'),
)
.join('\n');
}
return jsonEncode(err.response?.data);
}
void showErrorAlert(dynamic err) async {
final text = switch (err) {
String _ => err,
DioException _ => _parseRemoteError(err),
Exception _ => err.toString(),
_ => err.toString(),
};
FlutterPlatformAlert.showAlert(
windowTitle: 'somethingWentWrong'.tr(),
text: err.toString(),
text: text,
alertStyle: AlertButtonStyle.ok,
iconStyle: IconStyle.error,
);

View File

@ -1045,6 +1045,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.0"
modal_bottom_sheet:
dependency: "direct main"
description:
name: modal_bottom_sheet
sha256: eac66ef8cb0461bf069a38c5eb0fa728cee525a531a8304bd3f7b2185407c67e
url: "https://pub.dev"
source: hosted
version: "3.0.0"
nested:
dependency: transitive
description:
@ -1735,10 +1743,10 @@ packages:
dependency: transitive
description:
name: wakelock_plus
sha256: b6962cd9fc15e4843b573ba7b53bc46dd8a787594cf9ed5c5182581924656a58
sha256: a474e314c3e8fb5adef1f9ae2d247e57467ad557fa7483a2b895bc1b421c5678
url: "https://pub.dev"
source: hosted
version: "1.3.1"
version: "1.3.2"
wakelock_plus_platform_interface:
dependency: transitive
description:

View File

@ -79,6 +79,7 @@ dependencies:
image_picker_platform_interface: ^2.10.1
image_picker_android: ^0.8.12+23
super_context_menu: ^0.9.0-dev.6
modal_bottom_sheet: ^3.0.0
dev_dependencies:
flutter_test: