Login signup fixes (#1277)

* made button same height when loading, added timeout to language settings / avatar page

* fix: make button click always work, shrink inputs and add more space between logo and page content

* fix: don't open keyboard automatically in signup/login pages

* fix: make language dropdown hint text accurate

* feat: if a user logs in with SSO, allow them to set their username in the account setup page
pull/1544/head
ggurdin 11 months ago committed by GitHub
parent 940b65d007
commit c8bf68e4cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -4647,5 +4647,6 @@
"letsStart": "Let's start",
"pleaseAgreeToTOS": "Please agree to the Terms and Conditions",
"pleaseEnterEmail": "Please enter a valid email address.",
"pleaseSelectALanguage": "Please select a language"
"pleaseSelectALanguage": "Please select a language",
"myBaseLanguage": "My base language"
}

@ -1,6 +1,7 @@
import 'dart:async';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:fluffychat/pangea/constants/local.key.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/pages/sign_up/pangea_login_view.dart';
import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
@ -146,6 +147,8 @@ class LoginController extends State<Login> {
password: passwordController.text,
initialDeviceDisplayName: PlatformInfos.clientName,
);
MatrixState.pangeaController.pStoreService
.save(PLocalKey.loginType, 'password');
// #Pangea
GoogleAnalytics.login("pangea", loginRes.userId);
// Pangea#

@ -86,8 +86,8 @@ class LoginView extends StatelessWidget {
child: TextField(
readOnly: controller.loading,
autocorrect: false,
autofocus: true,
// #Pangea
// autofocus: true,
// onChanged: controller.checkWellKnownWithCoolDown,
// Pangea#
controller: controller.usernameController,

@ -9,4 +9,5 @@ class PLocalKey {
static const String justInputtedCode = 'justInputtedCode';
static const String availableSubscriptionInfo = 'availableSubscriptionInfo';
static const String showedUpdateDialog = 'showedUpdateDialog';
static const String loginType = 'loginType';
}

@ -29,7 +29,8 @@ class LanguageController {
_userL2Code != LanguageKeys.unknownLanguage;
LanguageModel? get systemLanguage {
final String systemLang = Platform.localeName.split('-').first;
if (Platform.localeName.length < 2) return null;
final String systemLang = Platform.localeName.substring(0, 2);
return PangeaLanguage.byLangCode(systemLang);
}

@ -89,21 +89,15 @@ class PangeaSsoButtonState extends State<PangeaSsoButton> {
depressed: _loading,
error: _error,
loading: _loading,
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
widget.provider.asset,
height: 20,
width: 20,
colorFilter: ColorFilter.mode(
Theme.of(context).colorScheme.onPrimary,
BlendMode.srcIn,
),
),
const SizedBox(width: 10),
Text(widget.title),
],
title: widget.title,
icon: SvgPicture.asset(
widget.provider.asset,
height: 20,
width: 20,
colorFilter: ColorFilter.mode(
Theme.of(context).colorScheme.onPrimary,
BlendMode.srcIn,
),
),
onPressed: _runSSOLogin,
);

@ -3,7 +3,9 @@ import 'package:fluffychat/pangea/widgets/pressable_button.dart';
import 'package:flutter/material.dart';
class FullWidthButton extends StatefulWidget {
final Widget title;
final String title;
final Widget? icon;
final void Function()? onPressed;
final bool depressed;
final String? error;
@ -13,6 +15,7 @@ class FullWidthButton extends StatefulWidget {
const FullWidthButton({
required this.title,
required this.onPressed,
this.icon,
this.depressed = false,
this.error,
this.loading = false,
@ -31,7 +34,7 @@ class FullWidthButtonState extends State<FullWidthButton> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.fromLTRB(4, 4, 4, widget.error == null ? 4 : 0),
padding: EdgeInsets.fromLTRB(6, 6, 6, widget.error == null ? 6 : 0),
child: AnimatedOpacity(
duration: FluffyThemes.animationDuration,
opacity: widget.enabled ? 1 : 0.5,
@ -40,40 +43,44 @@ class FullWidthButtonState extends State<FullWidthButton> {
onPressed: widget.onPressed,
borderRadius: BorderRadius.circular(36),
color: Theme.of(context).colorScheme.primary,
child: Builder(
builder: (context) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: widget.enabled
? Theme.of(context).colorScheme.primary
: Theme.of(context).disabledColor,
foregroundColor: Theme.of(context).colorScheme.onPrimary,
disabledForegroundColor:
Theme.of(context).colorScheme.onPrimary,
textStyle: const TextStyle(fontSize: 18),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(36),
),
),
onPressed: widget.enabled
? () => ButtonPressedNotification().dispatch(context)
: null,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
widget.loading
? const Expanded(
child: SizedBox(
height: 18,
child:
Center(child: LinearProgressIndicator()),
child: Container(
// internal padding
padding: const EdgeInsets.symmetric(horizontal: 16),
height: 50,
decoration: BoxDecoration(
color: widget.enabled
? Theme.of(context).colorScheme.primary
: Theme.of(context).disabledColor,
borderRadius: BorderRadius.circular(36),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
widget.loading
? const Expanded(
child: SizedBox(
height: 18,
child: Center(child: LinearProgressIndicator()),
),
)
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (widget.icon != null) widget.icon!,
if (widget.icon != null)
const SizedBox(width: 10),
Text(
widget.title,
style: TextStyle(
color:
Theme.of(context).colorScheme.onPrimary,
fontSize: 16,
),
)
: widget.title,
],
),
);
},
),
],
),
],
),
),
),
),
@ -104,7 +111,6 @@ class FullWidthButtonState extends State<FullWidthButton> {
class FullWidthTextField extends StatelessWidget {
final String hintText;
final bool autocorrect;
final bool autofocus;
final bool obscureText;
final TextInputAction? textInputAction;
final TextInputType? keyboardType;
@ -115,7 +121,6 @@ class FullWidthTextField extends StatelessWidget {
const FullWidthTextField({
required this.hintText,
this.autocorrect = false,
this.autofocus = false,
this.obscureText = false,
this.textInputAction,
this.keyboardType,
@ -128,11 +133,10 @@ class FullWidthTextField extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(4.0),
padding: const EdgeInsets.all(6.0),
child: TextFormField(
obscureText: obscureText,
autocorrect: autocorrect,
autofocus: autofocus,
textInputAction: textInputAction,
keyboardType: keyboardType,
decoration: InputDecoration(

@ -12,11 +12,11 @@ class LoginOrSignupView extends StatelessWidget {
return PangeaLoginScaffold(
children: [
FullWidthButton(
title: Text(L10n.of(context).createAnAccount),
title: L10n.of(context).createAnAccount,
onPressed: () => context.go('/home/signup'),
),
FullWidthButton(
title: Text(L10n.of(context).signIn),
title: L10n.of(context).signIn,
onPressed: () => context.go('/home/login'),
),
],

@ -32,14 +32,14 @@ class PangeaLoginScaffold extends StatelessWidget {
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 450,
maxWidth: 300,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 200,
height: 200,
width: 175,
height: 175,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.transparent,
@ -56,13 +56,13 @@ class PangeaLoginScaffold extends StatelessWidget {
),
),
),
const SizedBox(height: 16),
const SizedBox(height: 24),
if (showAppName)
Text(
AppConfig.applicationName,
style: Theme.of(context).textTheme.displaySmall,
),
const SizedBox(height: 16),
if (showAppName) const SizedBox(height: 12),
...children,
],
),

@ -19,7 +19,6 @@ class PangeaLoginView extends StatelessWidget {
children: [
FullWidthTextField(
hintText: L10n.of(context).username,
autofocus: true,
textInputAction: TextInputAction.next,
validator: (value) {
if (value == null || value.isEmpty) {
@ -44,16 +43,10 @@ class PangeaLoginView extends StatelessWidget {
errorText: controller.passwordError,
),
FullWidthButton(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
PangeaLogoSvg(
width: 20,
forceColor: Theme.of(context).colorScheme.onPrimary,
),
const SizedBox(width: 10),
Text(L10n.of(context).signIn),
],
title: L10n.of(context).signIn,
icon: PangeaLogoSvg(
width: 20,
forceColor: Theme.of(context).colorScheme.onPrimary,
),
onPressed: controller.enabledSignIn ? controller.login : null,
loading: controller.loading,

@ -19,18 +19,12 @@ class SignupPageView extends StatelessWidget {
return PangeaLoginScaffold(
children: [
FullWidthButton(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
PangeaLogoSvg(
width: 20,
forceColor: Theme.of(context).colorScheme.onPrimary,
),
const SizedBox(width: 10),
Text(L10n.of(context).signUpWithEmail),
],
),
title: L10n.of(context).signUpWithEmail,
onPressed: () => context.go('/home/signup/email'),
icon: PangeaLogoSvg(
width: 20,
forceColor: Theme.of(context).colorScheme.onPrimary,
),
),
PangeaSsoButton(
provider: SSOProvider.google,

@ -21,7 +21,6 @@ class SignupWithEmailView extends StatelessWidget {
children: [
FullWidthTextField(
hintText: L10n.of(context).yourUsername,
autofocus: true,
textInputAction: TextInputAction.next,
validator: (text) {
if (text == null || text.isEmpty) {
@ -47,16 +46,10 @@ class SignupWithEmailView extends StatelessWidget {
),
TosCheckbox(controller),
FullWidthButton(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
PangeaLogoSvg(
width: 20,
forceColor: Theme.of(context).colorScheme.onPrimary,
),
const SizedBox(width: 10),
Text(L10n.of(context).signUp),
],
title: L10n.of(context).signUp,
icon: PangeaLogoSvg(
width: 20,
forceColor: Theme.of(context).colorScheme.onPrimary,
),
onPressed: controller.enableSignUp ? controller.signup : null,
error: controller.error,

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:fluffychat/pangea/constants/local.key.dart';
import 'package:fluffychat/pangea/controllers/language_list_controller.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/models/language_model.dart';
@ -51,11 +52,26 @@ class UserSettingsState extends State<UserSettingsPage> {
: PangeaLanguage.byLangCode(systemLangCode);
}
bool canSetDisplayName = false;
TextEditingController displayNameController = TextEditingController();
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
@override
void initState() {
super.initState();
selectedTargetLanguage = _pangeaController.languageController.userL2;
selectedAvatarPath = avatarPaths.first;
final loginTypeEntry =
_pangeaController.pStoreService.read(PLocalKey.loginType);
if (loginTypeEntry is String && loginTypeEntry == 'sso') {
canSetDisplayName = true;
}
}
@override
void dispose() {
displayNameController.dispose();
super.dispose();
}
void setSelectedTargetLanguage(LanguageModel? language) {
@ -118,6 +134,15 @@ class UserSettingsState extends State<UserSettingsPage> {
}
}
Future<void> _setDisplayName() async {
final displayName = displayNameController.text.trim();
if (displayName.isEmpty) return;
final client = Matrix.of(context).client;
if (client.userID == null) return;
await client.setDisplayName(client.userID!, displayName);
}
Future<void> createUserInPangea() async {
setState(() => profileCreationError = null);
@ -128,10 +153,12 @@ class UserSettingsState extends State<UserSettingsPage> {
return;
}
if (!formKey.currentState!.validate()) return;
setState(() => loading = true);
try {
final updateFuture = [
_setDisplayName(),
_setAvatar(),
_pangeaController.subscriptionController.reinitialize(),
_pangeaController.userController.updateProfile(

@ -15,6 +15,8 @@ class UserSettingsView extends StatelessWidget {
super.key,
});
final double avatarSize = 55.0;
@override
Widget build(BuildContext context) {
final List<Widget> avatarOptions = controller.avatarPaths
@ -25,6 +27,7 @@ class UserSettingsView extends StatelessWidget {
onTap: () => controller.setSelectedAvatarPath(index),
path: path,
selected: controller.selectedAvatarIndex == index,
size: avatarSize,
),
);
})
@ -37,8 +40,8 @@ class UserSettingsView extends StatelessWidget {
child: InkWell(
onTap: controller.uploadAvatar,
child: Container(
width: 50,
height: 50,
width: avatarSize,
height: avatarSize,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
@ -51,7 +54,7 @@ class UserSettingsView extends StatelessWidget {
),
child: Icon(
Icons.upload,
color: Theme.of(context).colorScheme.onPrimary,
color: Theme.of(context).colorScheme.primary,
size: 30,
),
),
@ -59,48 +62,59 @@ class UserSettingsView extends StatelessWidget {
),
);
return PangeaLoginScaffold(
showAppName: false,
mainAssetPath: controller.selectedAvatarPath ?? "",
mainAssetBytes: controller.avatar,
children: [
Opacity(
opacity: 0.9,
child: Text(
L10n.of(context).chooseYourAvatar,
style: const TextStyle(
fontWeight: FontWeight.w100,
fontStyle: FontStyle.italic,
return Form(
key: controller.formKey,
child: PangeaLoginScaffold(
showAppName: false,
mainAssetPath: controller.selectedAvatarPath ?? "",
mainAssetBytes: controller.avatar,
children: [
Opacity(
opacity: 0.9,
child: Text(
L10n.of(context).chooseYourAvatar,
style: const TextStyle(
fontWeight: FontWeight.w100,
fontStyle: FontStyle.italic,
),
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: avatarOptions,
),
Padding(
padding: const EdgeInsets.all(8),
child: PLanguageDropdown(
languages: controller.targetOptions,
onChange: controller.setSelectedTargetLanguage,
initialLanguage: controller.selectedTargetLanguage,
isL2List: true,
error: controller.selectedLanguageError,
Wrap(
alignment: WrapAlignment.center,
children: avatarOptions,
),
),
FullWidthButton(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [Text(L10n.of(context).letsStart)],
Padding(
padding: const EdgeInsets.all(8),
child: PLanguageDropdown(
languages: controller.targetOptions,
onChange: controller.setSelectedTargetLanguage,
initialLanguage: controller.selectedTargetLanguage,
isL2List: true,
error: controller.selectedLanguageError,
),
),
onPressed: controller.selectedTargetLanguage != null
? controller.createUserInPangea
: null,
error: controller.profileCreationError,
loading: controller.loading,
enabled: controller.selectedTargetLanguage != null,
),
],
if (controller.canSetDisplayName)
FullWidthTextField(
hintText: L10n.of(context).username,
validator: (username) {
if (username == null || username.isEmpty) {
return L10n.of(context).pleaseChooseAUsername;
}
return null;
},
controller: controller.displayNameController,
),
FullWidthButton(
title: L10n.of(context).letsStart,
onPressed: controller.selectedTargetLanguage != null
? controller.createUserInPangea
: null,
error: controller.profileCreationError,
loading: controller.loading,
enabled: controller.selectedTargetLanguage != null,
),
],
),
);
}
}
@ -115,7 +129,7 @@ class AvatarOption extends StatelessWidget {
super.key,
required this.onTap,
required this.path,
this.size = 50.0,
this.size = 40.0,
this.selected = false,
});

@ -1,5 +1,6 @@
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pages/homeserver_picker/homeserver_picker.dart';
import 'package:fluffychat/pangea/constants/local.key.dart';
import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/matrix.dart';
@ -42,7 +43,6 @@ Future<void> pangeaSSOLoginAction(
result = await FlutterWebAuth2.authenticate(
url: url.toString(),
callbackUrlScheme: urlScheme,
options: const FlutterWebAuth2Options(),
);
} catch (err) {
if (err is PlatformException && err.code == 'CANCELED') {
@ -60,5 +60,6 @@ Future<void> pangeaSSOLoginAction(
token: token,
initialDeviceDisplayName: PlatformInfos.clientName,
);
MatrixState.pangeaController.pStoreService.save(PLocalKey.loginType, 'sso');
GoogleAnalytics.login(provider.name!, loginRes.userId);
}

@ -126,12 +126,8 @@ class PressableButtonState extends State<PressableButton>
@override
Widget build(BuildContext context) {
return NotificationListener<ButtonPressedNotification>(
onNotification: (notification) {
_onTapDown(null);
_onTapUp(null);
return true; // Stop the notification from bubbling further
},
return MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTapDown: _onTapDown,
onTapUp: _onTapUp,
@ -167,5 +163,3 @@ class PressableButtonState extends State<PressableButton>
);
}
}
class ButtonPressedNotification extends Notification {}

@ -23,65 +23,62 @@ class TosCheckboxState extends State<TosCheckbox>
with SingleTickerProviderStateMixin {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
InkWell(
onTap: () => UrlLauncher(context, AppConfig.termsOfServiceUrl)
.launchUrl(),
child: Padding(
padding: const EdgeInsets.only(left: 15),
child: RichText(
text: TextSpan(
text: L10n.of(context).iAgreeToThe,
children: [
TextSpan(
text: L10n.of(context).termsAndConditions,
style: const TextStyle(
decoration: TextDecoration.underline,
),
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
InkWell(
onTap: () => UrlLauncher(context, AppConfig.termsOfServiceUrl)
.launchUrl(),
child: Padding(
padding: const EdgeInsets.only(left: 15),
child: RichText(
text: TextSpan(
text: L10n.of(context).iAgreeToThe,
children: [
TextSpan(
text: L10n.of(context).termsAndConditions,
style: const TextStyle(
decoration: TextDecoration.underline,
),
],
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurface,
),
],
style: TextStyle(
fontSize: 14,
color: Theme.of(context).colorScheme.onSurface,
),
),
),
),
AnimatedSize(
duration: FluffyThemes.animationDuration,
child: widget.controller.signupError == null
? const SizedBox.shrink()
: Padding(
padding: const EdgeInsets.only(top: 4, left: 30),
child: Text(
widget.controller.signupError!,
style: TextStyle(
color: Theme.of(context).colorScheme.error,
fontSize: 12,
),
),
AnimatedSize(
duration: FluffyThemes.animationDuration,
child: widget.controller.signupError == null
? const SizedBox.shrink()
: Padding(
padding: const EdgeInsets.only(top: 4, left: 30),
child: Text(
widget.controller.signupError!,
style: TextStyle(
color: Theme.of(context).colorScheme.error,
fontSize: 12,
),
),
),
],
),
),
Checkbox(
value: widget.controller.isTnCChecked,
activeColor: Theme.of(context).colorScheme.primary,
onChanged: widget.controller.onTncChange,
),
),
],
),
],
),
),
Checkbox(
value: widget.controller.isTnCChecked,
activeColor: Theme.of(context).colorScheme.primary,
onChanged: widget.controller.onTncChange,
),
],
);
}
}

@ -76,7 +76,11 @@ class _PLanguageDropdownState extends State<PLanguageDropdown> {
children: [
const Icon(Icons.language_outlined),
const SizedBox(width: 10),
Text(L10n.of(context).iWantToLearn),
Text(
widget.isL2List
? L10n.of(context).iWantToLearn
: L10n.of(context).myBaseLanguage,
),
],
),
isExpanded: true,

Loading…
Cancel
Save