mirror of
				https://gitlab.com/famedly/fluffychat.git
				synced 2025-11-04 06:17:26 +01:00 
			
		
		
		
	feat: New registration workflow
This commit is contained in:
		
							parent
							
								
									afa1003e44
								
							
						
					
					
						commit
						f6082c5bac
					
				@ -1131,6 +1131,11 @@
 | 
			
		||||
    "type": "text",
 | 
			
		||||
    "placeholders": {}
 | 
			
		||||
  },
 | 
			
		||||
  "register": "Register",
 | 
			
		||||
  "@register": {
 | 
			
		||||
    "type": "text",
 | 
			
		||||
    "placeholders": {}
 | 
			
		||||
  },
 | 
			
		||||
  "logInTo": "Log in to {homeserver}",
 | 
			
		||||
  "@logInTo": {
 | 
			
		||||
    "type": "text",
 | 
			
		||||
@ -1938,8 +1943,15 @@
 | 
			
		||||
    "type": "text",
 | 
			
		||||
    "placeholders": {}
 | 
			
		||||
  },
 | 
			
		||||
  "useSSO": "Use single sign on",
 | 
			
		||||
  "@useSSO": {
 | 
			
		||||
  "loginWith": "Login with {brand}",
 | 
			
		||||
  "@loginWith": {
 | 
			
		||||
    "type": "text",
 | 
			
		||||
    "placeholders": {
 | 
			
		||||
      "brand": {}
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "singlesignon": "Single Sign on",
 | 
			
		||||
  "@singlesignon": {
 | 
			
		||||
    "type": "text",
 | 
			
		||||
    "placeholders": {}
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ abstract class AppConfig {
 | 
			
		||||
  static String get applicationName => _applicationName;
 | 
			
		||||
  static String _applicationWelcomeMessage;
 | 
			
		||||
  static String get applicationWelcomeMessage => _applicationWelcomeMessage;
 | 
			
		||||
  static String _defaultHomeserver = 'tchncs.de';
 | 
			
		||||
  static String _defaultHomeserver = 'matrix.org';
 | 
			
		||||
  static String get defaultHomeserver => _defaultHomeserver;
 | 
			
		||||
  static String jitsiInstance = 'https://meet.jit.si/';
 | 
			
		||||
  static double fontSizeFactor = 1.0;
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,6 @@ import 'package:fluffychat/pages/invitation_selection.dart';
 | 
			
		||||
import 'package:fluffychat/pages/settings_emotes.dart';
 | 
			
		||||
import 'package:fluffychat/pages/settings_multiple_emotes.dart';
 | 
			
		||||
import 'package:fluffychat/pages/sign_up.dart';
 | 
			
		||||
import 'package:fluffychat/pages/sign_up_password.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';
 | 
			
		||||
@ -204,11 +203,6 @@ class AppRoutes {
 | 
			
		||||
                widget: SignUp(),
 | 
			
		||||
                buildTransition: _fadeTransition,
 | 
			
		||||
                stackedRoutes: [
 | 
			
		||||
                  VWidget(
 | 
			
		||||
                    path: 'password/:username',
 | 
			
		||||
                    widget: SignUpPassword(),
 | 
			
		||||
                    buildTransition: _fadeTransition,
 | 
			
		||||
                  ),
 | 
			
		||||
                  VWidget(
 | 
			
		||||
                    path: '/login',
 | 
			
		||||
                    widget: Login(),
 | 
			
		||||
 | 
			
		||||
@ -59,6 +59,13 @@ abstract class FluffyThemes {
 | 
			
		||||
        borderRadius: BorderRadius.circular(AppConfig.borderRadius),
 | 
			
		||||
      ),
 | 
			
		||||
    ),
 | 
			
		||||
    outlinedButtonTheme: OutlinedButtonThemeData(
 | 
			
		||||
      style: OutlinedButton.styleFrom(
 | 
			
		||||
        shape: RoundedRectangleBorder(
 | 
			
		||||
          borderRadius: BorderRadius.circular(AppConfig.borderRadius),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    ),
 | 
			
		||||
    popupMenuTheme: PopupMenuThemeData(
 | 
			
		||||
      elevation: 4,
 | 
			
		||||
      shape: RoundedRectangleBorder(
 | 
			
		||||
@ -170,6 +177,13 @@ abstract class FluffyThemes {
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    ),
 | 
			
		||||
    outlinedButtonTheme: OutlinedButtonThemeData(
 | 
			
		||||
      style: OutlinedButton.styleFrom(
 | 
			
		||||
        shape: RoundedRectangleBorder(
 | 
			
		||||
          borderRadius: BorderRadius.circular(AppConfig.borderRadius),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    ),
 | 
			
		||||
    elevatedButtonTheme: ElevatedButtonThemeData(
 | 
			
		||||
      style: ElevatedButton.styleFrom(
 | 
			
		||||
        primary: AppConfig.primaryColor,
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,6 @@
 | 
			
		||||
import 'dart:async';
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:file_picker_cross/file_picker_cross.dart';
 | 
			
		||||
import 'package:fluffychat/config/app_config.dart';
 | 
			
		||||
import 'package:fluffychat/pages/views/sign_up_view.dart';
 | 
			
		||||
import 'package:fluffychat/utils/platform_infos.dart';
 | 
			
		||||
@ -11,7 +9,6 @@ import 'package:fluffychat/widgets/matrix.dart';
 | 
			
		||||
import 'package:flutter/cupertino.dart';
 | 
			
		||||
import 'package:flutter/foundation.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
			
		||||
import 'package:uni_links/uni_links.dart';
 | 
			
		||||
import 'package:url_launcher/url_launcher.dart';
 | 
			
		||||
@ -26,13 +23,18 @@ class SignUp extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class SignUpController extends State<SignUp> {
 | 
			
		||||
  final TextEditingController usernameController = TextEditingController();
 | 
			
		||||
  String usernameError;
 | 
			
		||||
  bool loading = false;
 | 
			
		||||
  static MatrixFile avatar;
 | 
			
		||||
 | 
			
		||||
  LoginTypes _loginTypes;
 | 
			
		||||
  Map<String, dynamic> _rawLoginTypes;
 | 
			
		||||
  bool registrationSupported;
 | 
			
		||||
  StreamSubscription _intentDataStreamSubscription;
 | 
			
		||||
  List<IdentityProvider> get identityProviders {
 | 
			
		||||
    if (!ssoLoginSupported) return [];
 | 
			
		||||
    final rawProviders = _rawLoginTypes.tryGetList('flows').singleWhere(
 | 
			
		||||
        (flow) =>
 | 
			
		||||
            flow['type'] == AuthenticationTypes.sso)['identity_providers'];
 | 
			
		||||
    return (rawProviders as List)
 | 
			
		||||
        .map((json) => IdentityProvider.fromJson(json))
 | 
			
		||||
        .toList();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _loginWithToken(String token) {
 | 
			
		||||
    if (token?.isEmpty ?? true) return;
 | 
			
		||||
@ -82,82 +84,68 @@ class SignUpController extends State<SignUp> {
 | 
			
		||||
    _intentDataStreamSubscription?.cancel();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool get passwordLoginSupported => _loginTypes.flows
 | 
			
		||||
      .any((flow) => flow.type == AuthenticationTypes.password);
 | 
			
		||||
  bool get passwordLoginSupported =>
 | 
			
		||||
      Matrix.of(context)
 | 
			
		||||
          .client
 | 
			
		||||
          .supportedLoginTypes
 | 
			
		||||
          .contains(AuthenticationTypes.password) &&
 | 
			
		||||
      _rawLoginTypes
 | 
			
		||||
          .tryGetList('flows')
 | 
			
		||||
          .any((flow) => flow['type'] == AuthenticationTypes.password);
 | 
			
		||||
 | 
			
		||||
  bool get ssoLoginSupported =>
 | 
			
		||||
      _loginTypes.flows.any((flow) => flow.type == AuthenticationTypes.sso);
 | 
			
		||||
      Matrix.of(context)
 | 
			
		||||
          .client
 | 
			
		||||
          .supportedLoginTypes
 | 
			
		||||
          .contains(AuthenticationTypes.sso) &&
 | 
			
		||||
      _rawLoginTypes
 | 
			
		||||
          .tryGetList('flows')
 | 
			
		||||
          .any((flow) => flow['type'] == AuthenticationTypes.sso);
 | 
			
		||||
 | 
			
		||||
  Future<LoginTypes> getLoginTypes() async {
 | 
			
		||||
    _loginTypes ??= await Matrix.of(context).client.getLoginFlows();
 | 
			
		||||
    return _loginTypes;
 | 
			
		||||
  Future<Map<String, dynamic>> getLoginTypes() async {
 | 
			
		||||
    _rawLoginTypes ??= await Matrix.of(context).client.request(
 | 
			
		||||
          RequestType.GET,
 | 
			
		||||
          '/client/r0/login',
 | 
			
		||||
        );
 | 
			
		||||
    if (registrationSupported == null) {
 | 
			
		||||
      try {
 | 
			
		||||
        await Matrix.of(context).client.register();
 | 
			
		||||
        registrationSupported = true;
 | 
			
		||||
      } on MatrixException catch (e) {
 | 
			
		||||
        registrationSupported = e.requireAdditionalAuthentication ?? false;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return _rawLoginTypes;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void ssoLoginAction() {
 | 
			
		||||
    if (!kIsWeb && !PlatformInfos.isMobile) {
 | 
			
		||||
      ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
        SnackBar(
 | 
			
		||||
          content: Text(
 | 
			
		||||
              'Single sign on is not suppored on ${Platform.operatingSystem}'),
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  void ssoLoginAction(String id) {
 | 
			
		||||
    final redirectUrl = kIsWeb
 | 
			
		||||
        ? html.window.location.href
 | 
			
		||||
        : AppConfig.appOpenUrlScheme.toLowerCase() + '://sso';
 | 
			
		||||
    launch(
 | 
			
		||||
        '${Matrix.of(context).client.homeserver?.toString()}/_matrix/client/r0/login/sso/redirect?redirectUrl=${Uri.encodeQueryComponent(redirectUrl)}');
 | 
			
		||||
        '${Matrix.of(context).client.homeserver?.toString()}/_matrix/client/r0/login/sso/redirect/${Uri.encodeComponent(id)}?redirectUrl=${Uri.encodeQueryComponent(redirectUrl)}');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setAvatarAction() async {
 | 
			
		||||
    final file =
 | 
			
		||||
        await FilePickerCross.importFromStorage(type: FileTypeCross.image);
 | 
			
		||||
    if (file != null) {
 | 
			
		||||
      setState(
 | 
			
		||||
        () => avatar = MatrixFile(
 | 
			
		||||
          bytes: file.toUint8List(),
 | 
			
		||||
          name: file.fileName,
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void resetAvatarAction() => setState(() => avatar = null);
 | 
			
		||||
 | 
			
		||||
  void signUpAction([_]) async {
 | 
			
		||||
    final matrix = Matrix.of(context);
 | 
			
		||||
    if (usernameController.text.isEmpty) {
 | 
			
		||||
      setState(() => usernameError = L10n.of(context).pleaseChooseAUsername);
 | 
			
		||||
    } else {
 | 
			
		||||
      setState(() => usernameError = null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (usernameController.text.isEmpty) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    setState(() => loading = true);
 | 
			
		||||
 | 
			
		||||
    final preferredUsername =
 | 
			
		||||
        usernameController.text.toLowerCase().trim().replaceAll(' ', '-');
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      await matrix.client.checkUsernameAvailability(preferredUsername);
 | 
			
		||||
    } on MatrixException catch (exception) {
 | 
			
		||||
      setState(() => usernameError = exception.errorMessage);
 | 
			
		||||
      return setState(() => loading = false);
 | 
			
		||||
    } catch (exception) {
 | 
			
		||||
      setState(() => usernameError = exception.toString());
 | 
			
		||||
      return setState(() => loading = false);
 | 
			
		||||
    }
 | 
			
		||||
    setState(() => loading = false);
 | 
			
		||||
 | 
			
		||||
    VRouter.of(context).push(
 | 
			
		||||
      '/signup/password/${Uri.encodeComponent(preferredUsername)}',
 | 
			
		||||
      queryParameters: {'displayname': usernameController.text},
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
  void signUpAction() => launch(
 | 
			
		||||
      '${Matrix.of(context).client.homeserver?.toString()}/_matrix/static/client/register');
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) => SignUpView(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class IdentityProvider {
 | 
			
		||||
  final String id;
 | 
			
		||||
  final String name;
 | 
			
		||||
  final String icon;
 | 
			
		||||
  final String brand;
 | 
			
		||||
 | 
			
		||||
  IdentityProvider({this.id, this.name, this.icon, this.brand});
 | 
			
		||||
 | 
			
		||||
  factory IdentityProvider.fromJson(Map<String, dynamic> json) =>
 | 
			
		||||
      IdentityProvider(
 | 
			
		||||
        id: json['id'],
 | 
			
		||||
        name: json['name'],
 | 
			
		||||
        icon: json['icon'],
 | 
			
		||||
        brand: json['brand'],
 | 
			
		||||
      );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,118 +0,0 @@
 | 
			
		||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:email_validator/email_validator.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:fluffychat/pages/sign_up.dart';
 | 
			
		||||
import 'package:fluffychat/utils/get_client_secret.dart';
 | 
			
		||||
import 'package:fluffychat/pages/views/sign_up_password_view.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:fluffychat/widgets/matrix.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
import 'package:vrouter/vrouter.dart';
 | 
			
		||||
import '../utils/platform_infos.dart';
 | 
			
		||||
 | 
			
		||||
class SignUpPassword extends StatefulWidget {
 | 
			
		||||
  const SignUpPassword();
 | 
			
		||||
  @override
 | 
			
		||||
  SignUpPasswordController createState() => SignUpPasswordController();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class SignUpPasswordController extends State<SignUpPassword> {
 | 
			
		||||
  final TextEditingController passwordController = TextEditingController();
 | 
			
		||||
  final TextEditingController emailController = TextEditingController();
 | 
			
		||||
  String passwordError;
 | 
			
		||||
  String emailError;
 | 
			
		||||
  bool loading = false;
 | 
			
		||||
  bool showPassword = true;
 | 
			
		||||
 | 
			
		||||
  void toggleShowPassword() => setState(() => showPassword = !showPassword);
 | 
			
		||||
 | 
			
		||||
  void signUpAction() async {
 | 
			
		||||
    final matrix = Matrix.of(context);
 | 
			
		||||
    if (passwordController.text.isEmpty) {
 | 
			
		||||
      setState(() => passwordError = L10n.of(context).pleaseEnterYourPassword);
 | 
			
		||||
    } else {
 | 
			
		||||
      setState(() => passwordError = emailError = null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (passwordController.text.isEmpty) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      setState(() => loading = true);
 | 
			
		||||
      if (emailController.text.isNotEmpty) {
 | 
			
		||||
        emailController.text = emailController.text.trim();
 | 
			
		||||
        if (!EmailValidator.validate(emailController.text)) {
 | 
			
		||||
          setState(() => emailError = L10n.of(context).invalidEmail);
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        matrix.currentClientSecret = getClientSecret(30);
 | 
			
		||||
        Logs().d('Request email token');
 | 
			
		||||
        matrix.currentThreepidCreds = await matrix.client.requestEmailToken(
 | 
			
		||||
          emailController.text,
 | 
			
		||||
          matrix.currentClientSecret,
 | 
			
		||||
          1,
 | 
			
		||||
        );
 | 
			
		||||
        if (OkCancelResult.ok !=
 | 
			
		||||
            await showOkCancelAlertDialog(
 | 
			
		||||
              useRootNavigator: false,
 | 
			
		||||
              context: context,
 | 
			
		||||
              message: L10n.of(context).weSentYouAnEmail,
 | 
			
		||||
              okLabel: L10n.of(context).confirm,
 | 
			
		||||
              cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
            )) {
 | 
			
		||||
          matrix.currentClientSecret = matrix.currentThreepidCreds = null;
 | 
			
		||||
          setState(() => loading = false);
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      final waitForLogin = matrix.client.onLoginStateChanged.stream.first;
 | 
			
		||||
      final username = VRouter.of(context).pathParameters['username'];
 | 
			
		||||
 | 
			
		||||
      await matrix.client.uiaRequestBackground((auth) => matrix.client.register(
 | 
			
		||||
            username: username,
 | 
			
		||||
            password: passwordController.text,
 | 
			
		||||
            initialDeviceDisplayName: PlatformInfos.clientName,
 | 
			
		||||
            auth: auth,
 | 
			
		||||
          ));
 | 
			
		||||
      if (matrix.currentClientSecret != null &&
 | 
			
		||||
          matrix.currentThreepidCreds != null) {
 | 
			
		||||
        Logs().d('Add third party identifier');
 | 
			
		||||
        await matrix.client.add3PID(
 | 
			
		||||
          matrix.currentClientSecret,
 | 
			
		||||
          matrix.currentThreepidCreds.sid,
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
      await waitForLogin;
 | 
			
		||||
    } catch (exception) {
 | 
			
		||||
      setState(() => emailError = exception.toString());
 | 
			
		||||
      return setState(() => loading = false);
 | 
			
		||||
    }
 | 
			
		||||
    await matrix.client.onLoginStateChanged.stream
 | 
			
		||||
        .firstWhere((l) => l == LoginState.logged);
 | 
			
		||||
    final displayname = VRouter.of(context).queryParameters['displayname'];
 | 
			
		||||
    if (displayname != null) {
 | 
			
		||||
      try {
 | 
			
		||||
        await matrix.client.setDisplayName(matrix.client.userID, displayname);
 | 
			
		||||
      } catch (exception) {
 | 
			
		||||
        ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
            SnackBar(content: Text(L10n.of(context).couldNotSetDisplayname)));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (SignUpController.avatar != null) {
 | 
			
		||||
      try {
 | 
			
		||||
        await matrix.client.setAvatar(SignUpController.avatar);
 | 
			
		||||
      } catch (exception) {
 | 
			
		||||
        ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
            SnackBar(content: Text(L10n.of(context).couldNotSetAvatar)));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (mounted) setState(() => loading = false);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) => SignUpPasswordView(this);
 | 
			
		||||
}
 | 
			
		||||
@ -1,84 +0,0 @@
 | 
			
		||||
import 'package:fluffychat/pages/sign_up_password.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:fluffychat/widgets/layouts/one_page_card.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
 | 
			
		||||
class SignUpPasswordView extends StatelessWidget {
 | 
			
		||||
  final SignUpPasswordController controller;
 | 
			
		||||
 | 
			
		||||
  const SignUpPasswordView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return OnePageCard(
 | 
			
		||||
      child: Scaffold(
 | 
			
		||||
        appBar: AppBar(
 | 
			
		||||
          elevation: 0,
 | 
			
		||||
          leading: controller.loading ? Container() : BackButton(),
 | 
			
		||||
          title: Text(
 | 
			
		||||
            L10n.of(context).chooseAStrongPassword,
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        body: ListView(
 | 
			
		||||
          children: <Widget>[
 | 
			
		||||
            Padding(
 | 
			
		||||
              padding: const EdgeInsets.all(12.0),
 | 
			
		||||
              child: TextField(
 | 
			
		||||
                controller: controller.passwordController,
 | 
			
		||||
                obscureText: !controller.showPassword,
 | 
			
		||||
                autofocus: true,
 | 
			
		||||
                readOnly: controller.loading,
 | 
			
		||||
                autocorrect: false,
 | 
			
		||||
                onSubmitted: (_) => controller.signUpAction,
 | 
			
		||||
                autofillHints:
 | 
			
		||||
                    controller.loading ? null : [AutofillHints.newPassword],
 | 
			
		||||
                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),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
            Padding(
 | 
			
		||||
              padding: const EdgeInsets.all(12.0),
 | 
			
		||||
              child: TextField(
 | 
			
		||||
                controller: controller.emailController,
 | 
			
		||||
                readOnly: controller.loading,
 | 
			
		||||
                autocorrect: false,
 | 
			
		||||
                keyboardType: TextInputType.emailAddress,
 | 
			
		||||
                onSubmitted: (_) => controller.signUpAction,
 | 
			
		||||
                decoration: InputDecoration(
 | 
			
		||||
                    prefixIcon: Icon(Icons.mail_outline_outlined),
 | 
			
		||||
                    errorText: controller.emailError,
 | 
			
		||||
                    hintText: 'email@example.com',
 | 
			
		||||
                    labelText: L10n.of(context).optionalAddEmail),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
            SizedBox(height: 12),
 | 
			
		||||
            Hero(
 | 
			
		||||
              tag: 'loginButton',
 | 
			
		||||
              child: Padding(
 | 
			
		||||
                padding: EdgeInsets.symmetric(horizontal: 12),
 | 
			
		||||
                child: ElevatedButton(
 | 
			
		||||
                  onPressed:
 | 
			
		||||
                      controller.loading ? null : controller.signUpAction,
 | 
			
		||||
                  child: controller.loading
 | 
			
		||||
                      ? LinearProgressIndicator()
 | 
			
		||||
                      : Text(L10n.of(context).createAccountNow),
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
import 'package:cached_network_image/cached_network_image.dart';
 | 
			
		||||
import 'package:vrouter/vrouter.dart';
 | 
			
		||||
import 'package:fluffychat/pages/sign_up.dart';
 | 
			
		||||
import 'package:fluffychat/widgets/fluffy_banner.dart';
 | 
			
		||||
@ -9,6 +10,8 @@ import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
import '../../utils/localized_exception_extension.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
 | 
			
		||||
class SignUpView extends StatelessWidget {
 | 
			
		||||
  final SignUpController controller;
 | 
			
		||||
 | 
			
		||||
@ -20,7 +23,6 @@ class SignUpView extends StatelessWidget {
 | 
			
		||||
      child: Scaffold(
 | 
			
		||||
        appBar: AppBar(
 | 
			
		||||
          elevation: 0,
 | 
			
		||||
          leading: controller.loading ? Container() : BackButton(),
 | 
			
		||||
          title: Text(
 | 
			
		||||
            Matrix.of(context)
 | 
			
		||||
                .client
 | 
			
		||||
@ -48,125 +50,83 @@ class SignUpView extends StatelessWidget {
 | 
			
		||||
                  tag: 'loginBanner',
 | 
			
		||||
                  child: FluffyBanner(),
 | 
			
		||||
                ),
 | 
			
		||||
                SizedBox(height: 16),
 | 
			
		||||
                if (controller.passwordLoginSupported) ...{
 | 
			
		||||
                  Padding(
 | 
			
		||||
                    padding: const EdgeInsets.symmetric(horizontal: 12.0),
 | 
			
		||||
                    child: TextField(
 | 
			
		||||
                      readOnly: controller.loading,
 | 
			
		||||
                      autocorrect: false,
 | 
			
		||||
                      controller: controller.usernameController,
 | 
			
		||||
                      onSubmitted: controller.signUpAction,
 | 
			
		||||
                      autofillHints: controller.loading
 | 
			
		||||
                          ? null
 | 
			
		||||
                          : [AutofillHints.newUsername],
 | 
			
		||||
                      decoration: InputDecoration(
 | 
			
		||||
                        prefixIcon: Padding(
 | 
			
		||||
                          padding: const EdgeInsets.only(
 | 
			
		||||
                            left: 12.0,
 | 
			
		||||
                            right: 22,
 | 
			
		||||
                          ),
 | 
			
		||||
                          child: Icon(Icons.account_circle_outlined),
 | 
			
		||||
                        ),
 | 
			
		||||
                        hintText: L10n.of(context).username,
 | 
			
		||||
                        errorText: controller.usernameError,
 | 
			
		||||
                        labelText: L10n.of(context).chooseAUsername,
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                  SizedBox(height: 8),
 | 
			
		||||
                  ListTile(
 | 
			
		||||
                    leading: CircleAvatar(
 | 
			
		||||
                      backgroundImage: SignUpController.avatar == null
 | 
			
		||||
                          ? null
 | 
			
		||||
                          : MemoryImage(SignUpController.avatar.bytes),
 | 
			
		||||
                      backgroundColor: SignUpController.avatar == null
 | 
			
		||||
                          ? Theme.of(context).brightness == Brightness.dark
 | 
			
		||||
                              ? Color(0xff121212)
 | 
			
		||||
                              : Colors.white
 | 
			
		||||
                          : Theme.of(context).secondaryHeaderColor,
 | 
			
		||||
                      child: SignUpController.avatar == null
 | 
			
		||||
                          ? Icon(Icons.camera_alt_outlined,
 | 
			
		||||
                              color: Theme.of(context).primaryColor)
 | 
			
		||||
                          : null,
 | 
			
		||||
                    ),
 | 
			
		||||
                    trailing: SignUpController.avatar == null
 | 
			
		||||
                        ? null
 | 
			
		||||
                        : Icon(
 | 
			
		||||
                            Icons.close,
 | 
			
		||||
                            color: Colors.red,
 | 
			
		||||
                          ),
 | 
			
		||||
                    title: Text(SignUpController.avatar == null
 | 
			
		||||
                        ? L10n.of(context).setAProfilePicture
 | 
			
		||||
                        : L10n.of(context).discardPicture),
 | 
			
		||||
                    onTap: SignUpController.avatar == null
 | 
			
		||||
                        ? controller.setAvatarAction
 | 
			
		||||
                        : controller.resetAvatarAction,
 | 
			
		||||
                  ),
 | 
			
		||||
                  SizedBox(height: 16),
 | 
			
		||||
                  Hero(
 | 
			
		||||
                    tag: 'loginButton',
 | 
			
		||||
                    child: Padding(
 | 
			
		||||
                      padding: EdgeInsets.symmetric(horizontal: 12),
 | 
			
		||||
                      child: ElevatedButton(
 | 
			
		||||
                        onPressed:
 | 
			
		||||
                            controller.loading ? null : controller.signUpAction,
 | 
			
		||||
                        child: controller.loading
 | 
			
		||||
                            ? LinearProgressIndicator()
 | 
			
		||||
                            : Text(L10n.of(context).signUp),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                  Row(
 | 
			
		||||
                    children: [
 | 
			
		||||
                      Expanded(
 | 
			
		||||
                          child: Container(
 | 
			
		||||
                        height: 1,
 | 
			
		||||
                        color: Theme.of(context).dividerColor,
 | 
			
		||||
                      )),
 | 
			
		||||
                      Padding(
 | 
			
		||||
                        padding: const EdgeInsets.all(12.0),
 | 
			
		||||
                        child: Text(L10n.of(context).or),
 | 
			
		||||
                      ),
 | 
			
		||||
                      Expanded(
 | 
			
		||||
                          child: Container(
 | 
			
		||||
                        height: 1,
 | 
			
		||||
                        color: Theme.of(context).dividerColor,
 | 
			
		||||
                      )),
 | 
			
		||||
                    ],
 | 
			
		||||
                  ),
 | 
			
		||||
                },
 | 
			
		||||
                Padding(
 | 
			
		||||
                  padding: EdgeInsets.symmetric(horizontal: 12),
 | 
			
		||||
                  child: Row(children: [
 | 
			
		||||
                    if (controller.passwordLoginSupported)
 | 
			
		||||
                      Expanded(
 | 
			
		||||
                        child: ElevatedButton(
 | 
			
		||||
                          style: ElevatedButton.styleFrom(
 | 
			
		||||
                            primary: Theme.of(context).secondaryHeaderColor,
 | 
			
		||||
                            onPrimary:
 | 
			
		||||
                                Theme.of(context).textTheme.bodyText1.color,
 | 
			
		||||
                  padding: const EdgeInsets.all(12.0),
 | 
			
		||||
                  child: Column(
 | 
			
		||||
                    mainAxisSize: MainAxisSize.min,
 | 
			
		||||
                    crossAxisAlignment: CrossAxisAlignment.stretch,
 | 
			
		||||
                    children: [
 | 
			
		||||
                      if (controller.ssoLoginSupported) ...{
 | 
			
		||||
                        for (final identityProvider
 | 
			
		||||
                            in controller.identityProviders)
 | 
			
		||||
                          OutlinedButton.icon(
 | 
			
		||||
                            onPressed: () =>
 | 
			
		||||
                                controller.ssoLoginAction(identityProvider.id),
 | 
			
		||||
                            icon: identityProvider.icon == null
 | 
			
		||||
                                ? Icon(Icons.web_outlined)
 | 
			
		||||
                                : CachedNetworkImage(
 | 
			
		||||
                                    imageUrl: Uri.parse(identityProvider.icon)
 | 
			
		||||
                                        .getDownloadLink(
 | 
			
		||||
                                            Matrix.of(context).client)
 | 
			
		||||
                                        .toString(),
 | 
			
		||||
                                    width: 24,
 | 
			
		||||
                                    height: 24,
 | 
			
		||||
                                  ),
 | 
			
		||||
                            label: Text(L10n.of(context).loginWith(
 | 
			
		||||
                                identityProvider.brand ??
 | 
			
		||||
                                    identityProvider.name ??
 | 
			
		||||
                                    L10n.of(context).singlesignon)),
 | 
			
		||||
                          ),
 | 
			
		||||
                          onPressed: () => context.vRouter.push('/login'),
 | 
			
		||||
                          child: Text(L10n.of(context).login),
 | 
			
		||||
                        ),
 | 
			
		||||
                        if (controller.registrationSupported ||
 | 
			
		||||
                            controller.passwordLoginSupported)
 | 
			
		||||
                          Row(children: [
 | 
			
		||||
                            Expanded(child: Divider()),
 | 
			
		||||
                            Padding(
 | 
			
		||||
                              padding: const EdgeInsets.all(12.0),
 | 
			
		||||
                              child: Text(L10n.of(context).or),
 | 
			
		||||
                            ),
 | 
			
		||||
                            Expanded(child: Divider()),
 | 
			
		||||
                          ]),
 | 
			
		||||
                      },
 | 
			
		||||
                      Row(
 | 
			
		||||
                        children: [
 | 
			
		||||
                          if (controller.passwordLoginSupported)
 | 
			
		||||
                            Expanded(
 | 
			
		||||
                              child: Container(
 | 
			
		||||
                                height: 64,
 | 
			
		||||
                                child: OutlinedButton.icon(
 | 
			
		||||
                                  onPressed: () =>
 | 
			
		||||
                                      context.vRouter.push('/login'),
 | 
			
		||||
                                  icon: Icon(Icons.login_outlined),
 | 
			
		||||
                                  label: Text(L10n.of(context).login),
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                            ),
 | 
			
		||||
                          if (controller.registrationSupported &&
 | 
			
		||||
                              controller.passwordLoginSupported)
 | 
			
		||||
                            SizedBox(width: 12),
 | 
			
		||||
                          if (controller.registrationSupported)
 | 
			
		||||
                            Expanded(
 | 
			
		||||
                              child: Container(
 | 
			
		||||
                                height: 64,
 | 
			
		||||
                                child: OutlinedButton.icon(
 | 
			
		||||
                                  onPressed: controller.signUpAction,
 | 
			
		||||
                                  icon: Icon(Icons.add_box_outlined),
 | 
			
		||||
                                  label: Text(L10n.of(context).register),
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                            ),
 | 
			
		||||
                        ],
 | 
			
		||||
                      ),
 | 
			
		||||
                    if (controller.passwordLoginSupported &&
 | 
			
		||||
                        controller.ssoLoginSupported)
 | 
			
		||||
                      SizedBox(width: 12),
 | 
			
		||||
                    if (controller.ssoLoginSupported)
 | 
			
		||||
                      Expanded(
 | 
			
		||||
                        child: ElevatedButton(
 | 
			
		||||
                          style: ElevatedButton.styleFrom(
 | 
			
		||||
                            primary: Theme.of(context).secondaryHeaderColor,
 | 
			
		||||
                            onPrimary:
 | 
			
		||||
                                Theme.of(context).textTheme.bodyText1.color,
 | 
			
		||||
                          ),
 | 
			
		||||
                          onPressed: controller.ssoLoginAction,
 | 
			
		||||
                          child: Text(L10n.of(context).useSSO),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                  ]),
 | 
			
		||||
                    ]
 | 
			
		||||
                        .map(
 | 
			
		||||
                          (widget) => Container(
 | 
			
		||||
                              height: 64,
 | 
			
		||||
                              padding: EdgeInsets.only(bottom: 12),
 | 
			
		||||
                              child: widget),
 | 
			
		||||
                        )
 | 
			
		||||
                        .toList(),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
              ]);
 | 
			
		||||
            }),
 | 
			
		||||
 | 
			
		||||
@ -1,13 +0,0 @@
 | 
			
		||||
import 'package:fluffychat/pages/sign_up_password.dart';
 | 
			
		||||
import 'package:fluffychat/main.dart';
 | 
			
		||||
import 'package:flutter_test/flutter_test.dart';
 | 
			
		||||
 | 
			
		||||
void main() {
 | 
			
		||||
  testWidgets('Test if the widget can be created', (WidgetTester tester) async {
 | 
			
		||||
    await tester.pumpWidget(
 | 
			
		||||
      FluffyChatApp(
 | 
			
		||||
        testWidget: SignUpPassword(),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user