refactor: Simplify login UX

pull/1287/head
krille-chan 11 months ago
parent f17b09f56c
commit dec588d0c0
No known key found for this signature in database

@ -2756,5 +2756,9 @@
} }
}, },
"changelog": "Changelog", "changelog": "Changelog",
"sendCanceled": "Sending canceled" "sendCanceled": "Sending canceled",
"loginWithMatrixId": "Login with Matrix-ID",
"discoverHomeservers": "Discover homeservers",
"whatIsAHomeserver": "What is a homeserver?",
"homeserverDescription": "All your data is stored on the homeserver, just like an email provider. You can choose which homeserver you want to use, while you can still communicate with everyone. Learn more at at https://matrix.org."
} }

@ -62,7 +62,7 @@ abstract class AppRoutes {
pageBuilder: (context, state) => defaultPageBuilder( pageBuilder: (context, state) => defaultPageBuilder(
context, context,
state, state,
const HomeserverPicker(), const HomeserverPicker(addMultiAccount: false),
), ),
redirect: loggedInRedirect, redirect: loggedInRedirect,
routes: [ routes: [
@ -242,7 +242,7 @@ abstract class AppRoutes {
pageBuilder: (context, state) => defaultPageBuilder( pageBuilder: (context, state) => defaultPageBuilder(
context, context,
state, state,
const HomeserverPicker(), const HomeserverPicker(addMultiAccount: true),
), ),
routes: [ routes: [
GoRoute( GoRoute(

@ -91,11 +91,10 @@ abstract class FluffyThemes {
), ),
inputDecorationTheme: InputDecorationTheme( inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder( border: OutlineInputBorder(
borderSide: BorderSide.none, borderRadius: BorderRadius.circular(AppConfig.borderRadius),
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
), ),
contentPadding: const EdgeInsets.all(12), contentPadding: const EdgeInsets.all(12),
filled: true, filled: false,
), ),
appBarTheme: AppBarTheme( appBarTheme: AppBarTheme(
toolbarHeight: FluffyThemes.isColumnMode(context) ? 72 : 56, toolbarHeight: FluffyThemes.isColumnMode(context) ? 72 : 56,
@ -114,13 +113,6 @@ abstract class FluffyThemes {
systemNavigationBarColor: colorScheme.surface, systemNavigationBarColor: colorScheme.surface,
), ),
), ),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
),
),
),
outlinedButtonTheme: OutlinedButtonThemeData( outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
side: BorderSide( side: BorderSide(
@ -145,9 +137,6 @@ abstract class FluffyThemes {
elevation: 0, elevation: 0,
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
textStyle: const TextStyle(fontSize: 16), textStyle: const TextStyle(fontSize: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
),
), ),
), ),
); );

@ -264,7 +264,9 @@ class BootstrapDialogState extends State<BootstrapDialog> {
hintStyle: TextStyle( hintStyle: TextStyle(
fontFamily: theme.textTheme.bodyLarge?.fontFamily, fontFamily: theme.textTheme.bodyLarge?.fontFamily,
), ),
hintText: L10n.of(context)!.recoveryKey, prefixIcon: const Icon(Icons.key_outlined),
labelText: L10n.of(context)!.recoveryKey,
hintText: 'Es** **** **** ****',
errorText: _recoveryKeyInputError, errorText: _recoveryKeyInputError,
errorMaxLines: 2, errorMaxLines: 2,
), ),

@ -54,6 +54,7 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
globalSearch: globalSearch, globalSearch: globalSearch,
), ),
decoration: InputDecoration( decoration: InputDecoration(
filled: true,
fillColor: theme.colorScheme.secondaryContainer, fillColor: theme.colorScheme.secondaryContainer,
border: OutlineInputBorder( border: OutlineInputBorder(
borderSide: BorderSide.none, borderSide: BorderSide.none,

@ -361,6 +361,7 @@ class _SpaceViewState extends State<SpaceView> {
onChanged: (_) => setState(() {}), onChanged: (_) => setState(() {}),
textInputAction: TextInputAction.search, textInputAction: TextInputAction.search,
decoration: InputDecoration( decoration: InputDecoration(
filled: true,
fillColor: theme.colorScheme.secondaryContainer, fillColor: theme.colorScheme.secondaryContainer,
border: OutlineInputBorder( border: OutlineInputBorder(
borderSide: BorderSide.none, borderSide: BorderSide.none,

@ -1,109 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/homeserver_picker/public_homeserver.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'homeserver_bottom_sheet.dart';
import 'homeserver_picker.dart';
class HomeserverAppBar extends StatelessWidget {
final HomeserverPickerController controller;
const HomeserverAppBar({super.key, required this.controller});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return TypeAheadField<PublicHomeserver>(
decorationBuilder: (context, child) => ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 256),
child: Material(
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
elevation: theme.appBarTheme.scrolledUnderElevation ?? 4,
shadowColor: theme.appBarTheme.shadowColor ?? Colors.black,
child: child,
),
),
emptyBuilder: (context) => ListTile(
leading: const Icon(Icons.search_outlined),
title: Text(L10n.of(context)!.nothingFound),
),
loadingBuilder: (context) => ListTile(
leading: const CircularProgressIndicator.adaptive(strokeWidth: 2),
title: Text(L10n.of(context)!.loadingPleaseWait),
),
errorBuilder: (context, error) => ListTile(
leading: const Icon(Icons.error_outlined),
title: Text(
error.toLocalizedString(context),
),
),
itemBuilder: (context, homeserver) => ListTile(
title: Text(homeserver.name),
subtitle: homeserver.description == null
? null
: Text(homeserver.description ?? ''),
trailing: IconButton(
icon: const Icon(Icons.info_outlined),
onPressed: () => showModalBottomSheet(
context: context,
builder: (_) => HomeserverBottomSheet(
homeserver: homeserver,
),
),
),
),
suggestionsCallback: (pattern) async {
pattern = pattern.toLowerCase().trim();
final homeservers = await controller.loadHomeserverList();
final matches = homeservers
.where(
(homeserver) =>
homeserver.name.toLowerCase().contains(pattern) ||
(homeserver.description?.toLowerCase().contains(pattern) ??
false),
)
.toList();
if (pattern.contains('.') &&
pattern.split('.').any((part) => part.isNotEmpty) &&
!matches.any((homeserver) => homeserver.name == pattern)) {
matches.add(PublicHomeserver(name: pattern));
}
return matches;
},
onSelected: (suggestion) {
controller.homeserverController.text = suggestion.name;
controller.checkHomeserverAction();
},
controller: controller.homeserverController,
builder: (context, textEditingController, focusNode) => TextField(
enabled: !controller.isLoggingIn,
controller: textEditingController,
focusNode: focusNode,
decoration: InputDecoration(
prefixIcon: Navigator.of(context).canPop()
? IconButton(
onPressed: Navigator.of(context).pop,
icon: const Icon(Icons.arrow_back),
)
: null,
fillColor: FluffyThemes.isColumnMode(context)
? theme.colorScheme.surface
// ignore: deprecated_member_use
: theme.colorScheme.surfaceVariant,
prefixText: '${L10n.of(context)!.homeserver}: ',
hintText: L10n.of(context)!.enterYourHomeserver,
suffixIcon: const Icon(Icons.search),
),
textInputAction: TextInputAction.search,
onSubmitted: controller.checkHomeserverAction,
autocorrect: false,
),
);
}
}

@ -1,53 +0,0 @@
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:fluffychat/pages/homeserver_picker/public_homeserver.dart';
class HomeserverBottomSheet extends StatelessWidget {
final PublicHomeserver homeserver;
const HomeserverBottomSheet({required this.homeserver, super.key});
@override
Widget build(BuildContext context) {
final description = homeserver.description;
final registration = homeserver.regLink;
final jurisdiction = homeserver.staffJur;
final homeserverSoftware = homeserver.software;
return Scaffold(
appBar: AppBar(
title: Text(homeserver.name),
),
body: ListView(
children: [
if (description != null && description.isNotEmpty)
ListTile(
leading: const Icon(Icons.info_outlined),
title: Text(description),
),
if (jurisdiction != null && jurisdiction.isNotEmpty)
ListTile(
leading: const Icon(Icons.location_city_outlined),
title: Text(jurisdiction),
),
if (homeserverSoftware != null && homeserverSoftware.isNotEmpty)
ListTile(
leading: const Icon(Icons.domain_outlined),
title: Text(homeserverSoftware),
),
ListTile(
onTap: () => launchUrlString(homeserver.name),
leading: const Icon(Icons.link_outlined),
title: Text(homeserver.name),
),
if (registration != null)
ListTile(
onTap: () => launchUrlString(registration),
leading: const Icon(Icons.person_add_outlined),
title: Text(registration),
),
],
),
);
}
}

@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -16,7 +15,6 @@ import 'package:universal_html/html.dart' as html;
import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pages/homeserver_picker/homeserver_picker_view.dart'; import 'package:fluffychat/pages/homeserver_picker/homeserver_picker_view.dart';
import 'package:fluffychat/pages/homeserver_picker/public_homeserver.dart';
import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/app_lock.dart'; import 'package:fluffychat/widgets/app_lock.dart';
import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/matrix.dart';
@ -26,7 +24,8 @@ import 'package:fluffychat/utils/tor_stub.dart'
if (dart.library.html) 'package:tor_detector_web/tor_detector_web.dart'; if (dart.library.html) 'package:tor_detector_web/tor_detector_web.dart';
class HomeserverPicker extends StatefulWidget { class HomeserverPicker extends StatefulWidget {
const HomeserverPicker({super.key}); final bool addMultiAccount;
const HomeserverPicker({required this.addMultiAccount, super.key});
@override @override
HomeserverPickerController createState() => HomeserverPickerController(); HomeserverPickerController createState() => HomeserverPickerController();
@ -64,6 +63,21 @@ class HomeserverPickerController extends State<HomeserverPicker> {
String? _lastCheckedUrl; String? _lastCheckedUrl;
Timer? _checkHomeserverCooldown;
tryCheckHomeserverActionWithCooldown([_]) {
_checkHomeserverCooldown?.cancel();
_checkHomeserverCooldown = Timer(
const Duration(milliseconds: 500),
checkHomeserverAction,
);
}
tryCheckHomeserverActionWithoutCooldown([_]) {
_checkHomeserverCooldown?.cancel();
checkHomeserverAction();
}
/// Starts an analysis of the given homeserver. It uses the current domain and /// Starts an analysis of the given homeserver. It uses the current domain and
/// makes sure that it is prefixed with https. Then it searches for the /// makes sure that it is prefixed with https. Then it searches for the
/// well-known information and forwards to the login page depending on the /// well-known information and forwards to the login page depending on the
@ -74,7 +88,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
if (homeserverController.text == _lastCheckedUrl) return; if (homeserverController.text == _lastCheckedUrl) return;
_lastCheckedUrl = homeserverController.text; _lastCheckedUrl = homeserverController.text;
setState(() { setState(() {
error = _rawLoginTypes = loginFlows = null; error = loginFlows = null;
isLoading = true; isLoading = true;
}); });
@ -86,12 +100,6 @@ class HomeserverPickerController extends State<HomeserverPicker> {
final client = Matrix.of(context).getLoginClient(); final client = Matrix.of(context).getLoginClient();
final (_, _, loginFlows) = await client.checkHomeserver(homeserver); final (_, _, loginFlows) = await client.checkHomeserver(homeserver);
this.loginFlows = loginFlows; this.loginFlows = loginFlows;
if (supportsSso) {
_rawLoginTypes = await client.request(
RequestType.GET,
'/client/v3/login',
);
}
} catch (e) { } catch (e) {
setState(() => error = (e).toLocalizedString(context)); setState(() => error = (e).toLocalizedString(context));
} finally { } finally {
@ -113,9 +121,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
bool get supportsPasswordLogin => _supportsFlow('m.login.password'); bool get supportsPasswordLogin => _supportsFlow('m.login.password');
Map<String, dynamic>? _rawLoginTypes; void ssoLoginAction() async {
void ssoLoginAction(String? id) async {
final redirectUrl = kIsWeb final redirectUrl = kIsWeb
? Uri.parse(html.window.location.href) ? Uri.parse(html.window.location.href)
.resolveUri( .resolveUri(
@ -127,7 +133,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
: 'http://localhost:3001//login'; : 'http://localhost:3001//login';
final url = Matrix.of(context).getLoginClient().homeserver!.replace( final url = Matrix.of(context).getLoginClient().homeserver!.replace(
path: '/_matrix/client/v3/login/sso/redirect${id == null ? '' : '/$id'}', path: '/_matrix/client/v3/login/sso/redirect',
queryParameters: {'redirectUrl': redirectUrl}, queryParameters: {'redirectUrl': redirectUrl},
); );
@ -164,39 +170,6 @@ class HomeserverPickerController extends State<HomeserverPicker> {
} }
} }
List<IdentityProvider>? get identityProviders {
final loginTypes = _rawLoginTypes;
if (loginTypes == null) return null;
final List? rawProviders =
loginTypes.tryGetList('flows')?.singleWhereOrNull(
(flow) => flow['type'] == AuthenticationTypes.sso,
)['identity_providers'] ??
[
{'id': null},
];
if (rawProviders == null) return null;
final list =
rawProviders.map((json) => IdentityProvider.fromJson(json)).toList();
if (PlatformInfos.isCupertinoStyle) {
list.sort((a, b) => a.brand == 'apple' ? -1 : 1);
}
return list;
}
List<PublicHomeserver>? cachedHomeservers;
Future<List<PublicHomeserver>> loadHomeserverList() async {
if (cachedHomeservers != null) return cachedHomeservers!;
final result = await Matrix.of(context)
.getLoginClient()
.httpClient
.get(AppConfig.homeserverList);
final resultJson = jsonDecode(result.body)['public_servers'] as List;
final homeserverList =
resultJson.map((json) => PublicHomeserver.fromJson(json)).toList();
return cachedHomeservers = homeserverList;
}
void login() => context.push( void login() => context.push(
'${GoRouter.of(context).routeInformationProvider.value.uri.path}/login', '${GoRouter.of(context).routeInformationProvider.value.uri.path}/login',
); );

@ -1,15 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/widgets/layouts/login_scaffold.dart'; import 'package:fluffychat/widgets/layouts/login_scaffold.dart';
import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/matrix.dart';
import '../../config/themes.dart'; import '../../config/themes.dart';
import '../../widgets/mxc_image.dart';
import 'homeserver_app_bar.dart';
import 'homeserver_picker.dart'; import 'homeserver_picker.dart';
class HomeserverPickerView extends StatelessWidget { class HomeserverPickerView extends StatelessWidget {
@ -21,22 +19,14 @@ class HomeserverPickerView extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
final identityProviders = controller.identityProviders;
final errorText = controller.error;
final publicHomeserver = controller.cachedHomeservers?.singleWhereOrNull(
(homeserver) =>
homeserver.name ==
controller.homeserverController.text.trim().toLowerCase(),
);
final regLink = publicHomeserver?.regLink;
return LoginScaffold( return LoginScaffold(
enforceMobileMode: Matrix.of(context).client.isLogged(), enforceMobileMode: Matrix.of(context).client.isLogged(),
appBar: AppBar( appBar: controller.widget.addMultiAccount
titleSpacing: 12, ? AppBar(
automaticallyImplyLeading: false, centerTitle: true,
surfaceTintColor: theme.colorScheme.surface, title: Text(L10n.of(context)!.addAccount),
title: HomeserverAppBar(controller: controller), )
), : null,
body: Column( body: Column(
children: [ children: [
// display a prominent banner to import session for TOR browser // display a prominent banner to import session for TOR browser
@ -62,163 +52,132 @@ class HomeserverPickerView extends StatelessWidget {
), ),
), ),
), ),
Image.asset(
'assets/banner_transparent.png',
),
Expanded( Expanded(
child: controller.isLoading child: ListView(
? const Center(child: CircularProgressIndicator.adaptive()) padding: const EdgeInsets.only(
: ListView( top: 16.0,
children: [ right: 8.0,
if (errorText != null) ...[ left: 8.0,
const SizedBox(height: 12), bottom: 16.0,
const Center( ),
child: Icon( children: [
Icons.error_outline, Padding(
size: 48, padding: const EdgeInsets.all(16.0),
color: Colors.orange, child: TextField(
), onChanged: controller.tryCheckHomeserverActionWithCooldown,
), onEditingComplete:
const SizedBox(height: 12), controller.tryCheckHomeserverActionWithoutCooldown,
Center( onSubmitted:
child: Text( controller.tryCheckHomeserverActionWithoutCooldown,
errorText, onTap: controller.tryCheckHomeserverActionWithCooldown,
textAlign: TextAlign.center, controller: controller.homeserverController,
style: TextStyle( decoration: InputDecoration(
color: theme.colorScheme.error, prefixIcon: controller.isLoading
fontSize: 18, ? Container(
), width: 16,
), height: 16,
), alignment: Alignment.center,
Center( child: const SizedBox(
child: Text( width: 16,
L10n.of(context)! height: 16,
.pleaseTryAgainLaterOrChooseDifferentServer, child: CircularProgressIndicator.adaptive(
textAlign: TextAlign.center, strokeWidth: 2,
style: TextStyle( ),
color: theme.colorScheme.error, ),
fontSize: 12, )
), : const Icon(Icons.search_outlined),
), filled: false,
), border: OutlineInputBorder(
const SizedBox(height: 36), borderRadius:
] else BorderRadius.circular(AppConfig.borderRadius),
Padding( ),
padding: const EdgeInsets.only( hintText: AppConfig.defaultHomeserver,
top: 0.0, labelText: L10n.of(context)!.homeserver,
right: 8.0, errorText: controller.error,
left: 8.0, suffixIcon: IconButton(
bottom: 16.0, onPressed: () {
), showDialog(
child: Image.asset( context: context,
'assets/banner_transparent.png', builder: (context) => AlertDialog.adaptive(
), title: Text(L10n.of(context)!.whatIsAHomeserver),
), content: Linkify(
if (identityProviders != null) ...[ text: L10n.of(context)!.homeserverDescription,
...identityProviders.map( ),
(provider) => _LoginButton( actions: [
icon: provider.icon == null TextButton(
? const Icon( onPressed: () => launchUrl(
Icons.open_in_new_outlined, Uri.https('servers.joinmatrix.org'),
size: 16,
)
: Material(
borderRadius: BorderRadius.circular(
AppConfig.borderRadius,
),
clipBehavior: Clip.hardEdge,
child: MxcImage(
placeholder: (_) => const Icon(
Icons.open_in_new_outlined,
size: 16,
),
uri: Uri.parse(provider.icon!),
width: 24,
height: 24,
isThumbnail: false,
//isThumbnail: false,
),
), ),
label: L10n.of(context)!.signInWith( child: Text(
provider.name ?? L10n.of(context)!.discoverHomeservers,
provider.brand ?? ),
L10n.of(context)!.singlesignon, ),
TextButton(
onPressed: Navigator.of(context).pop,
child: Text(L10n.of(context)!.close),
),
],
), ),
onPressed: () => );
controller.ssoLoginAction(provider.id), },
), icon: const Icon(Icons.info_outlined),
),
],
if (controller.supportsPasswordLogin)
_LoginButton(
onPressed: controller.login,
label: L10n.of(context)!.signInWithPassword,
icon: const Icon(Icons.lock_open_outlined, size: 16),
),
if (regLink != null)
_LoginButton(
onPressed: () => launchUrlString(regLink),
icon: const Icon(
Icons.open_in_new_outlined,
size: 16,
),
label: L10n.of(context)!.register,
),
_LoginButton(
onPressed: controller.restoreBackup,
label: L10n.of(context)!.hydrate,
withBorder: false,
), ),
const SizedBox(height: 16), ),
],
), ),
), ),
], if (controller.supportsPasswordLogin || controller.supportsSso)
), Padding(
); padding: const EdgeInsets.symmetric(
} horizontal: 16.0,
} vertical: 8.0,
),
class _LoginButton extends StatelessWidget { child: ElevatedButton(
final Widget? icon; style: ElevatedButton.styleFrom(
final String label; backgroundColor: theme.colorScheme.primary,
final void Function() onPressed; foregroundColor: theme.colorScheme.onPrimary,
final bool withBorder; ),
onPressed: controller.isLoggingIn || controller.isLoading
const _LoginButton({ ? null
this.icon, : controller.supportsSso
required this.label, ? controller.ssoLoginAction
required this.onPressed, : controller.login,
this.withBorder = true, child: Text(L10n.of(context)!.connect),
}); ),
),
@override if (controller.supportsPasswordLogin && controller.supportsSso)
Widget build(BuildContext context) { Padding(
final theme = Theme.of(context); padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: TextButton(
final icon = this.icon; style: TextButton.styleFrom(
return Container( foregroundColor: theme.colorScheme.secondary,
margin: const EdgeInsets.only(bottom: 12), textStyle: theme.textTheme.labelMedium,
padding: const EdgeInsets.symmetric(horizontal: 16), ),
alignment: Alignment.center, onPressed: controller.isLoggingIn || controller.isLoading
child: SizedBox( ? null
width: double.infinity, : controller.login,
child: OutlinedButton.icon( child: Text(L10n.of(context)!.loginWithMatrixId),
style: OutlinedButton.styleFrom( ),
side: FluffyThemes.isColumnMode(context) ),
? BorderSide.none Padding(
: BorderSide( padding: const EdgeInsets.symmetric(horizontal: 16.0),
color: theme.colorScheme.outlineVariant, child: TextButton(
width: 1, 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),
), ),
shape: RoundedRectangleBorder( ),
borderRadius: BorderRadius.circular(99), ],
), ),
foregroundColor: theme.colorScheme.onSurface,
backgroundColor:
withBorder ? theme.colorScheme.surface : Colors.transparent,
), ),
onPressed: onPressed, ],
label: Text(label),
icon: icon ?? const SizedBox.shrink(),
),
), ),
); );
} }

@ -1,73 +0,0 @@
class PublicHomeserver {
final String name;
final String? clientDomain;
final String? isp;
final String? staffJur;
final bool? usingVanillaReg;
final String? description;
final String? regMethod;
final String? regLink;
final String? software;
final String? version;
final bool? captcha;
final bool? email;
final List<String>? languages;
final List<Object>? features;
final int? onlineStatus;
final String? serverDomain;
final int? verStatus;
final int? roomDirectory;
final bool? slidingSync;
final bool? ipv6;
const PublicHomeserver({
required this.name,
this.clientDomain,
this.isp,
this.staffJur,
this.usingVanillaReg,
this.description,
this.regMethod,
this.regLink,
this.software,
this.version,
this.captcha,
this.email,
this.languages,
this.features,
this.onlineStatus,
this.serverDomain,
this.verStatus,
this.roomDirectory,
this.slidingSync,
this.ipv6,
});
factory PublicHomeserver.fromJson(Map<String, dynamic> json) =>
PublicHomeserver(
name: json['name'],
clientDomain: json['client_domain'],
isp: json['isp'],
staffJur: json['staff_jur'],
usingVanillaReg: json['using_vanilla_reg'],
description: json['description'],
regMethod: json['reg_method'],
regLink: json['reg_link'],
software: json['software'],
version: json['version'],
captcha: json['captcha'],
email: json['email'],
languages: json['languages'] == null
? null
: List<String>.from(json['languages']),
features: json['features'] == null
? null
: List<Object>.from(json['features']),
onlineStatus: json['online_status'],
serverDomain: json['server_domain'],
verStatus: json['ver_status'],
roomDirectory: json['room_directory'],
slidingSync: json['sliding_sync'],
ipv6: json['ipv6'],
);
}

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/widgets/layouts/login_scaffold.dart'; import 'package:fluffychat/widgets/layouts/login_scaffold.dart';
import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/matrix.dart';
import 'login.dart'; import 'login.dart';
@ -24,11 +23,6 @@ class LoginView extends StatelessWidget {
final title = L10n.of(context)!.logInTo(homeserver); final title = L10n.of(context)!.logInTo(homeserver);
final titleParts = title.split(homeserver); final titleParts = title.split(homeserver);
final textFieldFillColor = FluffyThemes.isColumnMode(context)
? theme.colorScheme.surface
// ignore: deprecated_member_use
: theme.colorScheme.surfaceVariant;
return LoginScaffold( return LoginScaffold(
enforceMobileMode: Matrix.of(context).client.isLogged(), enforceMobileMode: Matrix.of(context).client.isLogged(),
appBar: AppBar( appBar: AppBar(
@ -73,8 +67,8 @@ class LoginView extends StatelessWidget {
prefixIcon: const Icon(Icons.account_box_outlined), prefixIcon: const Icon(Icons.account_box_outlined),
errorText: controller.usernameError, errorText: controller.usernameError,
errorStyle: const TextStyle(color: Colors.orange), errorStyle: const TextStyle(color: Colors.orange),
fillColor: textFieldFillColor, hintText: '@username:localpart',
hintText: L10n.of(context)!.emailOrUsername, labelText: L10n.of(context)!.emailOrUsername,
), ),
), ),
), ),
@ -94,7 +88,6 @@ class LoginView extends StatelessWidget {
prefixIcon: const Icon(Icons.lock_outlined), prefixIcon: const Icon(Icons.lock_outlined),
errorText: controller.passwordError, errorText: controller.passwordError,
errorStyle: const TextStyle(color: Colors.orange), errorStyle: const TextStyle(color: Colors.orange),
fillColor: textFieldFillColor,
suffixIcon: IconButton( suffixIcon: IconButton(
onPressed: controller.toggleShowPassword, onPressed: controller.toggleShowPassword,
icon: Icon( icon: Icon(
@ -104,21 +97,21 @@ class LoginView extends StatelessWidget {
color: Colors.black, color: Colors.black,
), ),
), ),
hintText: L10n.of(context)!.password, hintText: '******',
labelText: L10n.of(context)!.password,
), ),
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0), padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: ElevatedButton.icon( child: ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.primary, backgroundColor: theme.colorScheme.primary,
foregroundColor: theme.colorScheme.onPrimary, foregroundColor: theme.colorScheme.onPrimary,
), ),
onPressed: controller.loading ? null : controller.login, onPressed: controller.loading ? null : controller.login,
icon: const Icon(Icons.login_outlined), child: controller.loading
label: controller.loading
? const LinearProgressIndicator() ? const LinearProgressIndicator()
: Text(L10n.of(context)!.login), : Text(L10n.of(context)!.login),
), ),
@ -126,15 +119,14 @@ class LoginView extends StatelessWidget {
const SizedBox(height: 16), const SizedBox(height: 16),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0), padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: TextButton.icon( child: TextButton(
onPressed: controller.loading onPressed: controller.loading
? () {} ? () {}
: controller.passwordForgotten, : controller.passwordForgotten,
style: TextButton.styleFrom( style: TextButton.styleFrom(
foregroundColor: theme.colorScheme.error, foregroundColor: theme.colorScheme.error,
), ),
icon: const Icon(Icons.safety_check_outlined), child: Text(L10n.of(context)!.passwordForgotten),
label: Text(L10n.of(context)!.passwordForgotten),
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),

@ -78,7 +78,7 @@ class LoginScaffold extends StatelessWidget {
child: ConstrainedBox( child: ConstrainedBox(
constraints: isMobileMode constraints: isMobileMode
? const BoxConstraints() ? const BoxConstraints()
: const BoxConstraints(maxWidth: 480, maxHeight: 720), : const BoxConstraints(maxWidth: 480, maxHeight: 640),
child: BackdropFilter( child: BackdropFilter(
filter: ImageFilter.blur( filter: ImageFilter.blur(
sigmaX: 10.0, sigmaX: 10.0,

@ -2351,10 +2351,10 @@ packages:
dependency: "direct overridden" dependency: "direct overridden"
description: description:
name: win32 name: win32
sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" sha256: "015002c060f1ae9f41a818f2d5640389cc05283e368be19dc8d77cecb43c40c9"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.5.0" version: "5.5.3"
win32_registry: win32_registry:
dependency: transitive dependency: transitive
description: description:

@ -160,4 +160,4 @@ dependency_overrides:
git: git:
url: https://github.com/TheOneWithTheBraid/keyboard_shortcuts.git url: https://github.com/TheOneWithTheBraid/keyboard_shortcuts.git
ref: null-safety ref: null-safety
win32: 5.5.0 win32: 5.5.3

Loading…
Cancel
Save