diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index c09023dd..dcd9011a 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -1450,6 +1450,8 @@ "type": "text", "placeholders": {} }, + "newPasswordDescription": "In order to be able to recover your password, you should later add an email address to your account.", + "newUsernameDescription": "Your user ID will then have the format @username:servername", "noPasswordRecoveryDescription": "You have not added a way to recover your password yet.", "@noPasswordRecoveryDescription": { "type": "text", @@ -1564,11 +1566,7 @@ "type": "text", "placeholders": {} }, - "optionalAddEmail": "(Optional) Your email address", - "@optionalAddEmail": { - "type": "text", - "placeholders": {} - }, + "serverRequiresEmail": "This server needs to validate your email address for registration.", "optionalGroupName": "(Optional) Group name", "@optionalGroupName": { "type": "text", diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 5a554605..813b5eb7 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -7,6 +7,7 @@ import 'package:fluffychat/pages/settings_chat.dart'; import 'package:fluffychat/pages/settings_emotes.dart'; import 'package:fluffychat/pages/settings_multiple_emotes.dart'; import 'package:fluffychat/pages/settings_security.dart'; +import 'package:fluffychat/pages/signup.dart'; import 'package:fluffychat/widgets/layouts/side_view_layout.dart'; import 'package:fluffychat/widgets/layouts/two_column_layout.dart'; import 'package:fluffychat/pages/chat.dart'; @@ -220,6 +221,11 @@ class AppRoutes { widget: Login(), buildTransition: _fadeTransition, ), + VWidget( + path: '/signup', + widget: SignupPage(), + buildTransition: _fadeTransition, + ), VWidget( path: 'logs', widget: LogViewer(), diff --git a/lib/pages/homeserver_picker.dart b/lib/pages/homeserver_picker.dart index fc828d95..4e485a24 100644 --- a/lib/pages/homeserver_picker.dart +++ b/lib/pages/homeserver_picker.dart @@ -216,11 +216,7 @@ class HomeserverPickerController extends State { } } - void signUpAction() => launch( - '${Matrix.of(context).client.homeserver?.toString()}/_matrix/static/client/register', - forceSafariVC: true, - forceWebView: true, - ); + void signUpAction() => VRouter.of(context).to('/signup'); bool _initialized = false; diff --git a/lib/pages/login.dart b/lib/pages/login.dart index a43efec9..36397d0d 100644 --- a/lib/pages/login.dart +++ b/lib/pages/login.dart @@ -177,9 +177,9 @@ class LoginController extends State { final clientSecret = DateTime.now().millisecondsSinceEpoch.toString(); final response = await showFutureLoadingDialog( context: context, - future: () => Matrix.of(context).client.resetPasswordUsingEmail( - input.single, + future: () => Matrix.of(context).client.requestTokenToResetPasswordEmail( clientSecret, + input.single, sendAttempt++, ), ); diff --git a/lib/pages/settings_3pid.dart b/lib/pages/settings_3pid.dart index 4fc34050..e03045e9 100644 --- a/lib/pages/settings_3pid.dart +++ b/lib/pages/settings_3pid.dart @@ -33,9 +33,9 @@ class Settings3PidController extends State { final clientSecret = DateTime.now().millisecondsSinceEpoch.toString(); final response = await showFutureLoadingDialog( context: context, - future: () => Matrix.of(context).client.requestEmailToken( - input.single, + future: () => Matrix.of(context).client.requestTokenToRegisterEmail( clientSecret, + input.single, Settings3Pid.sendAttempt++, ), ); diff --git a/lib/pages/signup.dart b/lib/pages/signup.dart new file mode 100644 index 00000000..69ad4f6b --- /dev/null +++ b/lib/pages/signup.dart @@ -0,0 +1,58 @@ +import 'package:fluffychat/pages/views/signup_view.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import '../utils/localized_exception_extension.dart'; + +class SignupPage extends StatefulWidget { + const SignupPage({Key key}) : super(key: key); + + @override + SignupPageController createState() => SignupPageController(); +} + +class SignupPageController extends State { + final TextEditingController usernameController = TextEditingController(); + final TextEditingController passwordController = TextEditingController(); + String usernameError; + String passwordError; + bool loading = false; + bool showPassword = true; + + void toggleShowPassword() => setState(() => showPassword = !showPassword); + + void signup([_]) async { + usernameError = passwordError = null; + + if (usernameController.text.isEmpty) { + return setState( + () => usernameError = L10n.of(context).pleaseChooseAUsername); + } + if (passwordController.text.isEmpty) { + return setState( + () => passwordError = L10n.of(context).chooseAStrongPassword); + } + + setState(() => loading = true); + + try { + final client = Matrix.of(context).client; + await client.uiaRequestBackground( + (auth) => client.register( + username: usernameController.text, + password: passwordController.text, + initialDeviceDisplayName: PlatformInfos.clientName, + auth: auth, + ), + ); + } catch (e) { + passwordError = (e as Object).toLocalizedString(context); + } finally { + setState(() => loading = false); + } + } + + @override + Widget build(BuildContext context) => SignupPageView(this); +} diff --git a/lib/pages/views/signup_view.dart b/lib/pages/views/signup_view.dart new file mode 100644 index 00000000..8d77628c --- /dev/null +++ b/lib/pages/views/signup_view.dart @@ -0,0 +1,93 @@ +import 'package:fluffychat/widgets/layouts/one_page_card.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +import '../signup.dart'; + +class SignupPageView extends StatelessWidget { + final SignupPageController controller; + const SignupPageView(this.controller, {Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return OnePageCard( + child: Scaffold( + appBar: AppBar( + title: Text(L10n.of(context).signUp), + ), + body: ListView( + children: [ + ListTile( + title: Text(L10n.of(context).pleaseChooseAUsername), + subtitle: Text(L10n.of(context).newUsernameDescription), + ), + Padding( + padding: const EdgeInsets.all(12.0), + child: TextField( + readOnly: controller.loading, + autocorrect: false, + autofocus: true, + controller: controller.usernameController, + autofillHints: + controller.loading ? null : [AutofillHints.username], + decoration: InputDecoration( + prefixIcon: Icon(Icons.account_box_outlined), + hintText: L10n.of(context).username, + errorText: controller.usernameError, + labelText: L10n.of(context).username, + prefixText: '@', + suffixText: + ':${Matrix.of(context).client.homeserver.host}'), + ), + ), + Divider(), + ListTile( + title: Text(L10n.of(context).chooseAStrongPassword), + subtitle: Text(L10n.of(context).newPasswordDescription), + ), + Padding( + padding: const EdgeInsets.all(12.0), + child: TextField( + readOnly: controller.loading, + autocorrect: false, + autofillHints: + controller.loading ? null : [AutofillHints.password], + controller: controller.passwordController, + obscureText: !controller.showPassword, + onSubmitted: controller.signup, + decoration: InputDecoration( + prefixIcon: Icon(Icons.lock_outlined), + hintText: '****', + errorText: controller.passwordError, + suffixIcon: IconButton( + tooltip: L10n.of(context).showPassword, + icon: Icon(controller.showPassword + ? Icons.visibility_off_outlined + : Icons.visibility_outlined), + onPressed: controller.toggleShowPassword, + ), + labelText: L10n.of(context).password, + ), + ), + ), + Divider(), + SizedBox(height: 12), + Hero( + tag: 'loginButton', + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 12), + child: ElevatedButton( + onPressed: controller.loading ? null : controller.signup, + child: controller.loading + ? LinearProgressIndicator() + : Text(L10n.of(context).signUp), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/matrix.dart b/lib/widgets/matrix.dart index 23405ccf..5f004715 100644 --- a/lib/widgets/matrix.dart +++ b/lib/widgets/matrix.dart @@ -116,9 +116,6 @@ class MatrixState extends State with WidgetsBindingObserver { set cachedPassword(String p) => _cachedPassword = p; - String currentClientSecret; - RequestTokenResponse currentThreepidCreds; - void _onUiaRequest(UiaRequest uiaRequest) async { try { if (uiaRequest.state != UiaRequestState.waitForUser || @@ -152,21 +149,40 @@ class MatrixState extends State with WidgetsBindingObserver { ), ); case AuthenticationTypes.emailIdentity: - if (currentClientSecret == null || currentThreepidCreds == null) { + final emailInput = await showTextInputDialog( + context: context, + message: L10n.of(context).serverRequiresEmail, + okLabel: L10n.of(context).next, + cancelLabel: L10n.of(context).cancel, + textFields: [ + DialogTextField( + hintText: L10n.of(context).addEmail, + keyboardType: TextInputType.emailAddress, + ), + ], + ); + if (emailInput == null || emailInput.isEmpty) { return uiaRequest - .cancel(Exception('This server requires an email address')); + .cancel(Exception(L10n.of(context).serverRequiresEmail)); } + final clientSecret = + Matrix.of(context).client.generateUniqueTransactionId(); + final currentThreepidCreds = + await Matrix.of(context).client.requestTokenToRegisterEmail( + clientSecret, + emailInput.single, + 0, + ); final auth = AuthenticationThreePidCreds( session: uiaRequest.session, type: AuthenticationTypes.emailIdentity, threepidCreds: [ ThreepidCreds( sid: currentThreepidCreds.sid, - clientSecret: currentClientSecret, + clientSecret: clientSecret, ), ], ); - currentThreepidCreds = currentClientSecret = null; return uiaRequest.completeStage(auth); case AuthenticationTypes.dummy: return uiaRequest.completeStage( @@ -189,10 +205,7 @@ class MatrixState extends State with WidgetsBindingObserver { cancelLabel: L10n.of(widget.context).cancel, )) { return uiaRequest.completeStage( - AuthenticationData( - session: uiaRequest.session, - type: AuthenticationTypes.token, - ), + AuthenticationData(session: uiaRequest.session), ); } else { return uiaRequest.cancel(); diff --git a/pubspec.lock b/pubspec.lock index 4cb24d86..8b2f912a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -729,7 +729,7 @@ packages: name: matrix_api_lite url: "https://pub.dartlang.org" source: hosted - version: "0.4.2" + version: "0.4.3" matrix_link_text: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 2208e174..7fa84452 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -101,3 +101,4 @@ flutter: - asset: fonts/NotoSans/NotoSans-BoldItalic.ttf weight: 700 style: italic + \ No newline at end of file