mirror of
				https://gitlab.com/famedly/fluffychat.git
				synced 2025-11-03 22:07:23 +01:00 
			
		
		
		
	feat: Implement in-app signup
This commit is contained in:
		
							parent
							
								
									e303c8e232
								
							
						
					
					
						commit
						6984206d66
					
				@ -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",
 | 
			
		||||
 | 
			
		||||
@ -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(),
 | 
			
		||||
 | 
			
		||||
@ -216,11 +216,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -177,9 +177,9 @@ class LoginController extends State<Login> {
 | 
			
		||||
    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++,
 | 
			
		||||
          ),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@ -33,9 +33,9 @@ class Settings3PidController extends State<Settings3Pid> {
 | 
			
		||||
    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++,
 | 
			
		||||
          ),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										58
									
								
								lib/pages/signup.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								lib/pages/signup.dart
									
									
									
									
									
										Normal file
									
								
							@ -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<SignupPage> {
 | 
			
		||||
  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);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										93
									
								
								lib/pages/views/signup_view.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								lib/pages/views/signup_view.dart
									
									
									
									
									
										Normal file
									
								
							@ -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),
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -116,9 +116,6 @@ class MatrixState extends State<Matrix> 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<Matrix> 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<Matrix> 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();
 | 
			
		||||
 | 
			
		||||
@ -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:
 | 
			
		||||
 | 
			
		||||
@ -101,3 +101,4 @@ flutter:
 | 
			
		||||
        - asset: fonts/NotoSans/NotoSans-BoldItalic.ttf
 | 
			
		||||
          weight: 700
 | 
			
		||||
          style: italic
 | 
			
		||||
          
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user