From 45f61533eeed157c52cce2253d0f2428006ac33d Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 8 Dec 2024 15:10:35 +0800 Subject: [PATCH] :sparkles: Create account field validation --- assets/translations/en.json | 7 +- assets/translations/zh.json | 7 +- lib/screens/auth/register.dart | 156 +++++++++++------- macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 24 +++ pubspec.yaml | 2 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 8 files changed, 137 insertions(+), 65 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index 52625fe..7df810c 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -84,8 +84,12 @@ "fieldEmail": "Email address", "fieldPassword": "Password", "fieldDescription": "Description", + "fieldUsernameAlphanumOnly": "Username can only contain alphanumeric characters.", + "fieldUsernameLengthLimit": "Username must be between {} and {} characters.", "fieldUsernameCannotEditHint": "Username cannot be edited after created", "fieldUsernameLookupHint": "You can use username, phone number or email to login", + "fieldNicknameLengthLimit": "Nickname must be between {} and {} characters.", + "fieldEmailAddressMustBeValid": "Email address must be an email address.", "fieldFirstName": "First name", "fieldLastName": "Last name", "fieldBirthday": "Birthday", @@ -408,5 +412,6 @@ "accountDeletionSubmitted": "Account deletion request has been sent, you can check your inbox and follow the instructions in the email to complete the deletion operation.", "channelNewChannel": "New Channel", "channelNewDirectMessage": "New Direct Message", - "channelDirectMessageDescription": "Direct Message with {}" + "channelDirectMessageDescription": "Direct Message with {}", + "fieldCannotBeEmpty": "This field cannot be empty." } diff --git a/assets/translations/zh.json b/assets/translations/zh.json index b12534a..3259701 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -69,8 +69,12 @@ "fieldNickname": "显示名", "fieldEmail": "电子邮箱地址", "fieldPassword": "密码", + "fieldUsernameAlphanumOnly": "用户名只能包含英文大小写字母和数字。", + "fieldUsernameLengthLimit": "用户名必须在 {} 和 {} 之间。", "fieldUsernameCannotEditHint": "用户名在创建后无法修改", "fieldUsernameLookupHint": "支持用户名、电话号码或邮箱地址", + "fieldNicknameLengthLimit": "昵称必须在 {} 和 {} 之间。", + "fieldEmailAddressMustBeValid": "电子邮箱地址必须是一个电子邮箱地址。", "fieldFirstName": "名", "fieldLastName": "姓", "fieldBirthday": "生日", @@ -408,5 +412,6 @@ "accountDeletionSubmitted": "帐户删除申请已发出,你可以检查你的收件箱并根据邮件内的指示完成删除操作。", "channelNewChannel": "新建频道", "channelNewDirectMessage": "发起私信", - "channelDirectMessageDescription": "与 {} 的私聊" + "channelDirectMessageDescription": "与 {} 的私聊", + "fieldCannotBeEmpty": "此字段不能为空。" } diff --git a/lib/screens/auth/register.dart b/lib/screens/auth/register.dart index 718ee1a..e8ec7fb 100644 --- a/lib/screens/auth/register.dart +++ b/lib/screens/auth/register.dart @@ -1,4 +1,5 @@ import 'package:easy_localization/easy_localization.dart'; +import 'package:email_validator/email_validator.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; @@ -16,20 +17,21 @@ class RegisterScreen extends StatefulWidget { } class _RegisterScreenState extends State { + final GlobalKey _formKey = GlobalKey(); + final _emailController = TextEditingController(); final _usernameController = TextEditingController(); final _nicknameController = TextEditingController(); final _passwordController = TextEditingController(); void _performAction(BuildContext context) async { + if (!_formKey.currentState!.validate()) return; + final email = _emailController.value.text; final username = _usernameController.value.text; final nickname = _nicknameController.value.text; final password = _passwordController.value.text; - if (email.isEmpty || - username.isEmpty || - nickname.isEmpty || - password.isEmpty) { + if (email.isEmpty || username.isEmpty || nickname.isEmpty || password.isEmpty) { return; } @@ -42,8 +44,7 @@ class _RegisterScreenState extends State { 'password': password, }); - if (!mounted) return; - + if (!context.mounted) return; GoRouter.of(context).replaceNamed("authLogin"); } catch (err) { context.showErrorDialog(err); @@ -75,67 +76,96 @@ class _RegisterScreenState extends State { fontWeight: FontWeight.w900, ), ).tr().padding(left: 4, bottom: 16), - Column( - children: [ - TextField( - autocorrect: false, - enableSuggestions: false, - controller: _usernameController, - autofillHints: const [AutofillHints.username], - decoration: InputDecoration( - isDense: true, - border: const UnderlineInputBorder(), - labelText: 'fieldUsername'.tr(), + Form( + key: _formKey, + autovalidateMode: AutovalidateMode.onUserInteraction, + child: Column( + children: [ + TextFormField( + validator: (value) { + if (value == null || value.length < 4 || value.length > 32) { + return 'fieldUsernameLengthLimit'.tr(args: [4.toString(), 32.toString()]); + } + if (!RegExp(r'^[a-zA-Z0-9_]+$').hasMatch(value)) { + return 'fieldUsernameAlphanumOnly'.tr(); + } + return null; + }, + autocorrect: false, + enableSuggestions: false, + controller: _usernameController, + autofillHints: const [AutofillHints.username], + decoration: InputDecoration( + isDense: true, + border: const UnderlineInputBorder(), + labelText: 'fieldUsername'.tr(), + ), + onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), ), - onTapOutside: (_) => - FocusManager.instance.primaryFocus?.unfocus(), - ), - const Gap(12), - TextField( - autocorrect: false, - enableSuggestions: false, - controller: _nicknameController, - autofillHints: const [AutofillHints.nickname], - decoration: InputDecoration( - isDense: true, - border: const UnderlineInputBorder(), - labelText: 'fieldNickname'.tr(), + const Gap(12), + TextFormField( + validator: (value) { + if (value == null || value.length < 4 || value.length > 32) { + return 'fieldNicknameLengthLimit'.tr(args: [4.toString(), 32.toString()]); + } + return null; + }, + autocorrect: false, + enableSuggestions: false, + controller: _nicknameController, + autofillHints: const [AutofillHints.nickname], + decoration: InputDecoration( + isDense: true, + border: const UnderlineInputBorder(), + labelText: 'fieldNickname'.tr(), + ), + onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), ), - onTapOutside: (_) => - FocusManager.instance.primaryFocus?.unfocus(), - ), - const Gap(12), - TextField( - autocorrect: false, - enableSuggestions: false, - controller: _emailController, - autofillHints: const [AutofillHints.email], - decoration: InputDecoration( - isDense: true, - border: const UnderlineInputBorder(), - labelText: 'fieldEmail'.tr(), + const Gap(12), + TextFormField( + validator: (value) { + if (value == null || value.isEmpty) { + return 'fieldCannotBeEmpty'.tr(); + } + if (!EmailValidator.validate(value)) { + return 'fieldEmailAddressMustBeValid'.tr(); + } + return null; + }, + autocorrect: false, + enableSuggestions: false, + controller: _emailController, + autofillHints: const [AutofillHints.email], + decoration: InputDecoration( + isDense: true, + border: const UnderlineInputBorder(), + labelText: 'fieldEmail'.tr(), + ), + onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), ), - onTapOutside: (_) => - FocusManager.instance.primaryFocus?.unfocus(), - ), - const Gap(12), - TextField( - obscureText: true, - autocorrect: false, - enableSuggestions: false, - autofillHints: const [AutofillHints.password], - controller: _passwordController, - decoration: InputDecoration( - isDense: true, - border: const UnderlineInputBorder(), - labelText: 'fieldPassword'.tr(), + const Gap(12), + TextFormField( + validator: (value) { + if (value == null || value.isEmpty) { + return 'fieldCannotBeEmpty'.tr(); + } + return null; + }, + obscureText: true, + autocorrect: false, + enableSuggestions: false, + autofillHints: const [AutofillHints.password], + controller: _passwordController, + decoration: InputDecoration( + isDense: true, + border: const UnderlineInputBorder(), + labelText: 'fieldPassword'.tr(), + ), + onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), ), - onTapOutside: (_) => - FocusManager.instance.primaryFocus?.unfocus(), - onSubmitted: (_) => _performAction(context), - ), - ], - ).padding(horizontal: 7), + ], + ).padding(horizontal: 7), + ), const Gap(16), Align( alignment: Alignment.centerRight, diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 3916e41..ba6ed34 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -23,6 +23,7 @@ import pasteboard import path_provider_foundation import screen_brightness_macos import sentry_flutter +import share_plus import shared_preferences_foundation import sqflite_darwin import url_launcher_macos @@ -47,6 +48,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) ScreenBrightnessMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenBrightnessMacosPlugin")) SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin")) + SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 921d8ec..af581e5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -454,6 +454,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.0.2" + email_validator: + dependency: "direct main" + description: + name: email_validator + sha256: b19aa5d92fdd76fbc65112060c94d45ba855105a28bb6e462de7ff03b12fa1fb + url: "https://pub.dev" + source: hosted + version: "3.0.0" equatable: dependency: transitive description: @@ -1538,6 +1546,22 @@ packages: url: "https://pub.dev" source: hosted version: "8.10.1" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: "9c9bafd4060728d7cdb2464c341743adbd79d327cb067ec7afb64583540b47c8" + url: "https://pub.dev" + source: hosted + version: "10.1.2" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: c57c0bbfec7142e3a0f55633be504b796af72e60e3c791b44d5a017b985f7a48 + url: "https://pub.dev" + source: hosted + version: "5.0.1" shared_preferences: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index e2fb8c0..303dc71 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -96,6 +96,8 @@ dependencies: sliver_tools: ^0.2.12 bitsdojo_window: ^0.1.6 gal: ^2.3.0 + share_plus: ^10.1.2 + email_validator: ^3.0.0 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 9495a88..9cb69e0 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { @@ -51,6 +52,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("ScreenBrightnessWindowsPlugin")); SentryFlutterPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("SentryFlutterPlugin")); + SharePlusWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index e8a8b84..a4528f7 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -17,6 +17,7 @@ list(APPEND FLUTTER_PLUGIN_LIST permission_handler_windows screen_brightness_windows sentry_flutter + share_plus url_launcher_windows )