design: New login design

pull/1439/head
Krille 9 months ago
parent a18e2c4db7
commit 3c5855c2d1
No known key found for this signature in database
GPG Key ID: E067ECD60F1A0652

@ -2788,5 +2788,7 @@
}
},
"oneOfYourDevicesIsNotVerified": "One of your devices is not verified",
"noticeChatBackupDeviceVerification": "Note: When you connect all your devices to the chat backup, they are automatically verified."
"noticeChatBackupDeviceVerification": "Note: When you connect all your devices to the chat backup, they are automatically verified.",
"continueText": "Continue",
"welcomeText": "Hey Hey 👋 This is FluffyChat. You can sign in to any homeserver, which is compatible with https://matrix.org. And then chat with anyone. It's a huge decentralized messaging network!"
}

@ -11,6 +11,7 @@ import 'package:go_router/go_router.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:matrix/matrix.dart';
import 'package:universal_html/html.dart' as html;
import 'package:url_launcher/url_launcher_string.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pages/homeserver_picker/homeserver_picker_view.dart';
@ -83,29 +84,30 @@ class HomeserverPickerController extends State<HomeserverPicker> {
/// well-known information and forwards to the login page depending on the
/// login type.
Future<void> checkHomeserverAction([_]) async {
homeserverController.text =
final homeserverInput =
homeserverController.text.trim().toLowerCase().replaceAll(' ', '-');
if (homeserverController.text.isEmpty) {
if (homeserverInput.isEmpty || !homeserverInput.contains('.')) {
setState(() {
error = loginFlows = null;
isLoading = false;
Matrix.of(context).getLoginClient().homeserver = null;
_lastCheckedUrl = null;
});
return;
}
if (_lastCheckedUrl == homeserverController.text) return;
if (_lastCheckedUrl == homeserverInput) return;
_lastCheckedUrl = homeserverController.text;
_lastCheckedUrl = homeserverInput;
setState(() {
error = loginFlows = null;
isLoading = true;
});
try {
var homeserver = Uri.parse(homeserverController.text);
var homeserver = Uri.parse(homeserverInput);
if (homeserver.scheme.isEmpty) {
homeserver = Uri.https(homeserverController.text, '');
homeserver = Uri.https(homeserverInput, '');
}
final client = Matrix.of(context).getLoginClient();
final (_, _, loginFlows) = await client.checkHomeserver(homeserver);
@ -185,9 +187,15 @@ class HomeserverPickerController extends State<HomeserverPicker> {
}
}
void login() => context.push(
'${GoRouter.of(context).routeInformationProvider.value.uri.path}/login',
);
void login() async {
if (!supportsPasswordLogin) {
homeserverController.text = AppConfig.defaultHomeserver;
await checkHomeserverAction();
}
context.push(
'${GoRouter.of(context).routeInformationProvider.value.uri.path}/login',
);
}
@override
void initState() {
@ -223,8 +231,21 @@ class HomeserverPickerController extends State<HomeserverPicker> {
}
}
}
void onMoreAction(MoreLoginActions action) {
switch (action) {
case MoreLoginActions.passwordLogin:
login();
case MoreLoginActions.privacy:
launchUrlString(AppConfig.privacyUrl);
case MoreLoginActions.about:
PlatformInfos.showDialog(context);
}
}
}
enum MoreLoginActions { passwordLogin, privacy, about }
class IdentityProvider {
final String? id;
final String? name;

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/widgets/adaptive_dialog_action.dart';
@ -22,159 +23,228 @@ class HomeserverPickerView extends StatelessWidget {
return LoginScaffold(
enforceMobileMode: Matrix.of(context).client.isLogged(),
appBar: controller.widget.addMultiAccount
? AppBar(
centerTitle: true,
title: Text(L10n.of(context).addAccount),
)
: null,
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// display a prominent banner to import session for TOR browser
// users. This feature is just some UX sugar as TOR users are
// usually forced to logout as TOR browser is non-persistent
AnimatedContainer(
height: controller.isTorBrowser ? 64 : 0,
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(),
child: Material(
clipBehavior: Clip.hardEdge,
borderRadius:
const BorderRadius.vertical(bottom: Radius.circular(8)),
color: theme.colorScheme.surface,
child: ListTile(
leading: const Icon(Icons.vpn_key),
title: Text(L10n.of(context).hydrateTor),
subtitle: Text(L10n.of(context).hydrateTorLong),
trailing: const Icon(Icons.chevron_right_outlined),
onTap: controller.restoreBackup,
appBar: AppBar(
centerTitle: true,
title: Text(L10n.of(context).addAccount),
actions: [
PopupMenuButton<MoreLoginActions>(
onSelected: controller.onMoreAction,
itemBuilder: (_) => [
PopupMenuItem(
value: MoreLoginActions.passwordLogin,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.login_outlined),
const SizedBox(width: 12),
Text(L10n.of(context).loginWithMatrixId),
],
),
),
),
),
if (MediaQuery.of(context).size.height > 512)
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height / 4,
PopupMenuItem(
value: MoreLoginActions.privacy,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.privacy_tip_outlined),
const SizedBox(width: 12),
Text(L10n.of(context).privacy),
],
),
),
child: Image.asset(
'assets/banner_transparent.png',
alignment: Alignment.center,
repeat: ImageRepeat.repeat,
PopupMenuItem(
value: MoreLoginActions.about,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.info_outlined),
const SizedBox(width: 12),
Text(L10n.of(context).about),
],
),
),
),
Padding(
padding: const EdgeInsets.all(32.0),
child: TextField(
onChanged: controller.tryCheckHomeserverActionWithCooldown,
onEditingComplete:
controller.tryCheckHomeserverActionWithoutCooldown,
onSubmitted: controller.tryCheckHomeserverActionWithoutCooldown,
onTap: controller.tryCheckHomeserverActionWithCooldown,
controller: controller.homeserverController,
autocorrect: false,
keyboardType: TextInputType.url,
decoration: InputDecoration(
prefixIcon: controller.isLoading
? Container(
width: 16,
height: 16,
alignment: Alignment.center,
child: const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator.adaptive(
strokeWidth: 2,
),
],
),
],
),
body: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraints.maxHeight),
child: IntrinsicHeight(
child: Column(
children: [
// display a prominent banner to import session for TOR browser
// users. This feature is just some UX sugar as TOR users are
// usually forced to logout as TOR browser is non-persistent
AnimatedContainer(
height: controller.isTorBrowser ? 64 : 0,
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(),
child: Material(
clipBehavior: Clip.hardEdge,
borderRadius: const BorderRadius.vertical(
bottom: Radius.circular(8),
),
)
: const Icon(Icons.search_outlined),
filled: false,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
),
hintText: AppConfig.defaultHomeserver,
labelText: L10n.of(context).homeserver,
errorText: controller.error,
suffixIcon: IconButton(
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog.adaptive(
title: Text(L10n.of(context).whatIsAHomeserver),
content: Linkify(
text: L10n.of(context).homeserverDescription,
color: theme.colorScheme.surface,
child: ListTile(
leading: const Icon(Icons.vpn_key),
title: Text(L10n.of(context).hydrateTor),
subtitle: Text(L10n.of(context).hydrateTorLong),
trailing: const Icon(Icons.chevron_right_outlined),
onTap: controller.restoreBackup,
),
actions: [
AdaptiveDialogAction(
onPressed: () => launchUrl(
Uri.https('servers.joinmatrix.org'),
),
),
Container(
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Hero(
tag: 'info-logo',
child: Image.asset(
'./assets/banner_transparent.png',
fit: BoxFit.fitWidth,
),
),
),
const SizedBox(height: 32),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32.0),
child: SelectableLinkify(
text: L10n.of(context).welcomeText,
style: TextStyle(
color: theme.colorScheme.onSecondaryContainer,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
linkStyle: TextStyle(
color: theme.colorScheme.secondary,
decorationColor: theme.colorScheme.secondary,
),
onOpen: (link) => launchUrlString(link.url),
),
),
const Spacer(),
Padding(
padding: const EdgeInsets.all(32.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextField(
onChanged:
controller.tryCheckHomeserverActionWithCooldown,
onEditingComplete: controller
.tryCheckHomeserverActionWithoutCooldown,
onSubmitted: controller
.tryCheckHomeserverActionWithoutCooldown,
onTap:
controller.tryCheckHomeserverActionWithCooldown,
controller: controller.homeserverController,
autocorrect: false,
keyboardType: TextInputType.url,
decoration: InputDecoration(
prefixIcon: controller.isLoading
? Container(
width: 16,
height: 16,
alignment: Alignment.center,
child: const SizedBox(
width: 16,
height: 16,
child:
CircularProgressIndicator.adaptive(
strokeWidth: 2,
),
),
)
: const Icon(Icons.search_outlined),
filled: false,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(
AppConfig.borderRadius,
),
),
hintText: AppConfig.defaultHomeserver,
hintStyle: TextStyle(
color: theme.colorScheme.surfaceTint,
),
labelText: 'Sign in with:',
errorText: controller.error,
errorMaxLines: 4,
suffixIcon: IconButton(
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog.adaptive(
title: Text(
L10n.of(context).whatIsAHomeserver,
),
content: Linkify(
text: L10n.of(context)
.homeserverDescription,
),
actions: [
AdaptiveDialogAction(
onPressed: () => launchUrl(
Uri.https('servers.joinmatrix.org'),
),
child: Text(
L10n.of(context)
.discoverHomeservers,
),
),
AdaptiveDialogAction(
onPressed: Navigator.of(context).pop,
child: Text(L10n.of(context).close),
),
],
),
);
},
icon: const Icon(Icons.info_outlined),
),
),
child: Text(
L10n.of(context).discoverHomeservers,
),
const SizedBox(height: 32),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.primary,
foregroundColor: theme.colorScheme.onPrimary,
),
onPressed:
controller.isLoggingIn || controller.isLoading
? null
: controller.supportsSso
? controller.ssoLoginAction
: controller.supportsPasswordLogin
? controller.login
: null,
child: Text(L10n.of(context).continueText),
),
AdaptiveDialogAction(
onPressed: Navigator.of(context).pop,
child: Text(L10n.of(context).close),
TextButton(
style: TextButton.styleFrom(
foregroundColor: theme.colorScheme.secondary,
textStyle: theme.textTheme.labelMedium,
),
onPressed:
controller.isLoggingIn || controller.isLoading
? null
: controller.restoreBackup,
child: Text(L10n.of(context).hydrate),
),
],
),
);
},
icon: const Icon(Icons.info_outlined),
),
],
),
),
),
),
if (MediaQuery.of(context).size.height > 512) const Spacer(),
ListView(
shrinkWrap: true,
padding: const EdgeInsets.symmetric(
horizontal: 32.0,
vertical: 32.0,
),
children: [
TextButton(
style: TextButton.styleFrom(
textStyle: theme.textTheme.labelMedium,
foregroundColor: theme.colorScheme.secondary,
),
onPressed: controller.isLoggingIn || controller.isLoading
? null
: controller.restoreBackup,
child: Text(L10n.of(context).hydrate),
),
if (controller.supportsPasswordLogin && controller.supportsSso)
TextButton(
style: TextButton.styleFrom(
foregroundColor: theme.colorScheme.secondary,
textStyle: theme.textTheme.labelMedium,
),
onPressed: controller.isLoggingIn || controller.isLoading
? null
: controller.login,
child: Text(L10n.of(context).loginWithMatrixId),
),
const SizedBox(height: 8.0),
if (controller.supportsPasswordLogin || controller.supportsSso)
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.primary,
foregroundColor: theme.colorScheme.onPrimary,
),
onPressed: controller.isLoggingIn || controller.isLoading
? null
: controller.supportsSso
? controller.ssoLoginAction
: controller.login,
child: Text(L10n.of(context).next),
),
],
),
],
);
},
),
);
}

@ -49,7 +49,10 @@ class LoginView extends StatelessWidget {
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 8),
children: <Widget>[
Image.asset('assets/banner_transparent.png'),
Hero(
tag: 'info-logo',
child: Image.asset('assets/banner_transparent.png'),
),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
@ -67,7 +70,7 @@ class LoginView extends StatelessWidget {
prefixIcon: const Icon(Icons.account_box_outlined),
errorText: controller.usernameError,
errorStyle: const TextStyle(color: Colors.orange),
hintText: '@username:localpart',
hintText: '@username:domain',
labelText: L10n.of(context).emailOrUsername,
),
),

@ -44,17 +44,6 @@ class LoginScaffold extends StatelessWidget {
body: SafeArea(child: body),
backgroundColor:
isMobileMode ? null : theme.colorScheme.surface.withOpacity(0.8),
bottomNavigationBar: isMobileMode
? Material(
elevation: 4,
shadowColor: theme.colorScheme.onSurface,
child: const SafeArea(
child: _PrivacyButtons(
mainAxisAlignment: MainAxisAlignment.center,
),
),
)
: null,
);
if (isMobileMode) return scaffold;
return Container(

Loading…
Cancel
Save