feat: Nicer registration form
This commit is contained in:
parent
bc78647fb6
commit
b48cf2ecdc
@ -220,7 +220,10 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
||||
}
|
||||
}
|
||||
|
||||
void signUpAction() => VRouter.of(context).to('signup');
|
||||
void signUpAction() => VRouter.of(context).to(
|
||||
'signup',
|
||||
queryParameters: {'domain': domain},
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:vrouter/vrouter.dart';
|
||||
|
||||
import 'package:fluffychat/pages/views/signup_view.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
@ -17,29 +18,78 @@ class SignupPage extends StatefulWidget {
|
||||
class SignupPageController extends State<SignupPage> {
|
||||
final TextEditingController usernameController = TextEditingController();
|
||||
final TextEditingController passwordController = TextEditingController();
|
||||
String usernameError;
|
||||
String passwordError;
|
||||
final TextEditingController passwordController2 = TextEditingController();
|
||||
final TextEditingController emailController = TextEditingController();
|
||||
String error;
|
||||
bool loading = false;
|
||||
bool showPassword = true;
|
||||
bool showPassword = false;
|
||||
|
||||
void toggleShowPassword() => setState(() => showPassword = !showPassword);
|
||||
|
||||
String get domain => VRouter.of(context).queryParameters['domain'];
|
||||
|
||||
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
|
||||
|
||||
String usernameTextFieldValidator(String value) {
|
||||
usernameController.text =
|
||||
usernameController.text.trim().toLowerCase().replaceAll(' ', '_');
|
||||
if (value.isEmpty) {
|
||||
return L10n.of(context).pleaseChooseAUsername;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String password1TextFieldValidator(String value) {
|
||||
const minLength = 8;
|
||||
if (value.isEmpty) {
|
||||
return L10n.of(context).chooseAStrongPassword;
|
||||
}
|
||||
if (value.length < minLength) {
|
||||
return 'Please choose at least $minLength characters.';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String password2TextFieldValidator(String value) {
|
||||
if (value.isEmpty) {
|
||||
return L10n.of(context).chooseAStrongPassword;
|
||||
}
|
||||
if (value != passwordController.text) {
|
||||
return 'Passwords do not match!';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String emailTextFieldValidator(String value) {
|
||||
if (value.isNotEmpty && !value.contains('@')) {
|
||||
return 'Please enter a valid email address.';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void signup([_]) async {
|
||||
usernameError = passwordError = null;
|
||||
setState(() {
|
||||
error = null;
|
||||
});
|
||||
if (!formKey.currentState.validate()) return;
|
||||
|
||||
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);
|
||||
setState(() {
|
||||
loading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final client = Matrix.of(context).getLoginClient();
|
||||
final email = emailController.text;
|
||||
if (email.isNotEmpty) {
|
||||
Matrix.of(context).currentClientSecret =
|
||||
DateTime.now().millisecondsSinceEpoch.toString();
|
||||
Matrix.of(context).currentThreepidCreds =
|
||||
await client.requestTokenToRegisterEmail(
|
||||
Matrix.of(context).currentClientSecret,
|
||||
email,
|
||||
0,
|
||||
);
|
||||
}
|
||||
await client.uiaRequestBackground(
|
||||
(auth) => client.register(
|
||||
username: usernameController.text,
|
||||
@ -49,7 +99,7 @@ class SignupPageController extends State<SignupPage> {
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
passwordError = (e as Object).toLocalizedString(context);
|
||||
error = (e as Object).toLocalizedString(context);
|
||||
} finally {
|
||||
setState(() => loading = false);
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import 'package:fluffychat/widgets/layouts/one_page_card.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../signup.dart';
|
||||
|
||||
class SignupPageView extends StatelessWidget {
|
||||
@ -17,76 +16,116 @@ class SignupPageView extends StatelessWidget {
|
||||
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: const Icon(Icons.account_box_outlined),
|
||||
hintText: L10n.of(context).username,
|
||||
errorText: controller.usernameError,
|
||||
labelText: L10n.of(context).username,
|
||||
prefixText: '@',
|
||||
suffixText:
|
||||
':${Matrix.of(context).getLoginClient().homeserver.host}'),
|
||||
body: Form(
|
||||
key: controller.formKey,
|
||||
child: ListView(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: TextFormField(
|
||||
readOnly: controller.loading,
|
||||
autocorrect: false,
|
||||
controller: controller.usernameController,
|
||||
autofillHints:
|
||||
controller.loading ? null : [AutofillHints.username],
|
||||
validator: controller.usernameTextFieldValidator,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.account_circle_outlined),
|
||||
hintText: L10n.of(context).username,
|
||||
labelText: L10n.of(context).username,
|
||||
prefixText: '@',
|
||||
prefixStyle: const TextStyle(fontWeight: FontWeight.bold),
|
||||
suffixStyle: const TextStyle(fontWeight: FontWeight.w200),
|
||||
suffixText: ':${controller.domain}'),
|
||||
),
|
||||
),
|
||||
),
|
||||
const 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: const 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,
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: TextFormField(
|
||||
readOnly: controller.loading,
|
||||
autocorrect: false,
|
||||
autofillHints:
|
||||
controller.loading ? null : [AutofillHints.password],
|
||||
controller: controller.passwordController,
|
||||
obscureText: !controller.showPassword,
|
||||
validator: controller.password1TextFieldValidator,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.vpn_key_outlined),
|
||||
hintText: '****',
|
||||
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,
|
||||
),
|
||||
labelText: L10n.of(context).password,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
const SizedBox(height: 12),
|
||||
Hero(
|
||||
tag: 'loginButton',
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: ElevatedButton(
|
||||
onPressed: controller.loading ? null : controller.signup,
|
||||
child: controller.loading
|
||||
? const LinearProgressIndicator()
|
||||
: Text(L10n.of(context).signUp),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: TextFormField(
|
||||
readOnly: controller.loading,
|
||||
autocorrect: false,
|
||||
autofillHints:
|
||||
controller.loading ? null : [AutofillHints.password],
|
||||
controller: controller.passwordController2,
|
||||
obscureText: true,
|
||||
validator: controller.password2TextFieldValidator,
|
||||
decoration: const InputDecoration(
|
||||
prefixIcon: Icon(Icons.repeat_outlined),
|
||||
hintText: '****',
|
||||
labelText: 'Repeat password',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: TextFormField(
|
||||
readOnly: controller.loading,
|
||||
autocorrect: false,
|
||||
controller: controller.emailController,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
autofillHints:
|
||||
controller.loading ? null : [AutofillHints.username],
|
||||
validator: controller.emailTextFieldValidator,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.mail_outlined),
|
||||
labelText: L10n.of(context).addEmail,
|
||||
hintText: 'email@example.abc',
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
const SizedBox(height: 12),
|
||||
if (controller.error != null) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Text(
|
||||
controller.error,
|
||||
style: const TextStyle(color: Colors.red),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Divider(),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
Hero(
|
||||
tag: 'loginButton',
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: ElevatedButton(
|
||||
onPressed: controller.loading ? null : controller.signup,
|
||||
child: controller.loading
|
||||
? const LinearProgressIndicator()
|
||||
: Text(L10n.of(context).signUp),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -5,6 +5,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'uia_request_manager.dart';
|
||||
|
||||
extension LocalizedExceptionExtension on Object {
|
||||
String toLocalizedString(BuildContext context) {
|
||||
if (this is MatrixException) {
|
||||
@ -48,6 +50,7 @@ extension LocalizedExceptionExtension on Object {
|
||||
if (this is MatrixConnectionException || this is SocketException) {
|
||||
return L10n.of(context).noConnectionToTheServer;
|
||||
}
|
||||
if (this is UiaException) return toString();
|
||||
Logs().w('Something went wrong: ', this);
|
||||
return L10n.of(context).oopsSomethingWentWrong;
|
||||
}
|
||||
|
@ -1,18 +1,24 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
extension UiaRequestManager on MatrixState {
|
||||
Future uiaRequestHandler(UiaRequest uiaRequest) async {
|
||||
try {
|
||||
if (uiaRequest.state != UiaRequestState.waitForUser ||
|
||||
uiaRequest.nextStages.isEmpty) return;
|
||||
uiaRequest.nextStages.isEmpty) {
|
||||
Logs().d('Uia Request Stage: ${uiaRequest.state}');
|
||||
return;
|
||||
}
|
||||
final stage = uiaRequest.nextStages.first;
|
||||
Logs().d('Uia Request Stage: $stage');
|
||||
switch (stage) {
|
||||
case AuthenticationTypes.password:
|
||||
final input = cachedPassword ??
|
||||
@ -31,7 +37,9 @@ extension UiaRequestManager on MatrixState {
|
||||
],
|
||||
))
|
||||
?.single;
|
||||
if (input?.isEmpty ?? true) return;
|
||||
if (input?.isEmpty ?? true) {
|
||||
return uiaRequest.cancel();
|
||||
}
|
||||
return uiaRequest.completeStage(
|
||||
AuthenticationPassword(
|
||||
session: uiaRequest.session,
|
||||
@ -40,35 +48,18 @@ extension UiaRequestManager on MatrixState {
|
||||
),
|
||||
);
|
||||
case AuthenticationTypes.emailIdentity:
|
||||
final emailInput = await showTextInputDialog(
|
||||
context: navigatorContext,
|
||||
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(L10n.of(context).serverRequiresEmail));
|
||||
if (currentThreepidCreds == null || currentClientSecret == null) {
|
||||
return uiaRequest.cancel(
|
||||
UiaException(L10n.of(widget.context).serverRequiresEmail),
|
||||
);
|
||||
}
|
||||
final clientSecret = DateTime.now().millisecondsSinceEpoch.toString();
|
||||
final currentThreepidCreds = await client.requestTokenToRegisterEmail(
|
||||
clientSecret,
|
||||
emailInput.single,
|
||||
0,
|
||||
);
|
||||
final auth = AuthenticationThreePidCreds(
|
||||
session: uiaRequest.session,
|
||||
type: AuthenticationTypes.emailIdentity,
|
||||
threepidCreds: [
|
||||
ThreepidCreds(
|
||||
sid: currentThreepidCreds.sid,
|
||||
clientSecret: clientSecret,
|
||||
clientSecret: currentClientSecret,
|
||||
),
|
||||
],
|
||||
);
|
||||
@ -92,24 +83,41 @@ extension UiaRequestManager on MatrixState {
|
||||
),
|
||||
);
|
||||
default:
|
||||
await launch(
|
||||
client.homeserver.toString() +
|
||||
'/_matrix/client/r0/auth/$stage/fallback/web?session=${uiaRequest.session}',
|
||||
);
|
||||
if (OkCancelResult.ok ==
|
||||
await showOkCancelAlertDialog(
|
||||
useRootNavigator: false,
|
||||
message: L10n.of(context).pleaseFollowInstructionsOnWeb,
|
||||
context: navigatorContext,
|
||||
okLabel: L10n.of(context).next,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
)) {
|
||||
return uiaRequest.completeStage(
|
||||
AuthenticationData(session: uiaRequest.session),
|
||||
final url = Uri.parse(client.homeserver.toString() +
|
||||
'/_matrix/client/r0/auth/$stage/fallback/web?session=${uiaRequest.session}');
|
||||
if (PlatformInfos.isMobile) {
|
||||
final browser = UiaFallbackBrowser();
|
||||
browser.addMenuItem(
|
||||
ChromeSafariBrowserMenuItem(
|
||||
action: (_, __) {
|
||||
uiaRequest.cancel();
|
||||
},
|
||||
label: L10n.of(context).cancel,
|
||||
id: 0,
|
||||
),
|
||||
);
|
||||
await browser.open(url: url);
|
||||
await browser.whenClosed.stream.first;
|
||||
} else {
|
||||
return uiaRequest.cancel();
|
||||
launch(url.toString());
|
||||
if (OkCancelResult.ok ==
|
||||
await showOkCancelAlertDialog(
|
||||
useRootNavigator: false,
|
||||
message: L10n.of(context).pleaseFollowInstructionsOnWeb,
|
||||
context: navigatorContext,
|
||||
okLabel: L10n.of(context).next,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
)) {
|
||||
return uiaRequest.completeStage(
|
||||
AuthenticationData(session: uiaRequest.session),
|
||||
);
|
||||
} else {
|
||||
return uiaRequest.cancel();
|
||||
}
|
||||
}
|
||||
await uiaRequest.completeStage(
|
||||
AuthenticationData(session: uiaRequest.session),
|
||||
);
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logs().e('Error while background UIA', e, s);
|
||||
@ -117,3 +125,19 @@ extension UiaRequestManager on MatrixState {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UiaException implements Exception {
|
||||
final String reason;
|
||||
|
||||
UiaException(this.reason);
|
||||
|
||||
@override
|
||||
String toString() => reason;
|
||||
}
|
||||
|
||||
class UiaFallbackBrowser extends ChromeSafariBrowser {
|
||||
final StreamController<bool> whenClosed = StreamController<bool>.broadcast();
|
||||
|
||||
@override
|
||||
onClosed() => whenClosed.add(true);
|
||||
}
|
||||
|
@ -75,6 +75,9 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
||||
int getClientIndexByMatrixId(String matrixId) =>
|
||||
widget.clients.indexWhere((client) => client.userID == matrixId);
|
||||
|
||||
String currentClientSecret;
|
||||
RequestTokenResponse currentThreepidCreds;
|
||||
|
||||
int get _safeActiveClient {
|
||||
if (widget.clients.isEmpty) {
|
||||
widget.clients.add(getLoginClient());
|
||||
|
Loading…
Reference in New Issue
Block a user