refactor: Simplify login UX
This commit is contained in:
parent
f17b09f56c
commit
dec588d0c0
15 changed files with 172 additions and 486 deletions
|
|
@ -62,7 +62,7 @@ abstract class AppRoutes {
|
|||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const HomeserverPicker(),
|
||||
const HomeserverPicker(addMultiAccount: false),
|
||||
),
|
||||
redirect: loggedInRedirect,
|
||||
routes: [
|
||||
|
|
@ -242,7 +242,7 @@ abstract class AppRoutes {
|
|||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const HomeserverPicker(),
|
||||
const HomeserverPicker(addMultiAccount: true),
|
||||
),
|
||||
routes: [
|
||||
GoRoute(
|
||||
|
|
|
|||
|
|
@ -91,11 +91,10 @@ abstract class FluffyThemes {
|
|||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
),
|
||||
contentPadding: const EdgeInsets.all(12),
|
||||
filled: true,
|
||||
filled: false,
|
||||
),
|
||||
appBarTheme: AppBarTheme(
|
||||
toolbarHeight: FluffyThemes.isColumnMode(context) ? 72 : 56,
|
||||
|
|
@ -114,13 +113,6 @@ abstract class FluffyThemes {
|
|||
systemNavigationBarColor: colorScheme.surface,
|
||||
),
|
||||
),
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: TextButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||
),
|
||||
),
|
||||
),
|
||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||
style: OutlinedButton.styleFrom(
|
||||
side: BorderSide(
|
||||
|
|
@ -145,9 +137,6 @@ abstract class FluffyThemes {
|
|||
elevation: 0,
|
||||
padding: const EdgeInsets.all(16),
|
||||
textStyle: const TextStyle(fontSize: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -264,7 +264,9 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
|||
hintStyle: TextStyle(
|
||||
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,
|
||||
errorMaxLines: 2,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||
globalSearch: globalSearch,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: theme.colorScheme.secondaryContainer,
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
|
|
|
|||
|
|
@ -361,6 +361,7 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
onChanged: (_) => setState(() {}),
|
||||
textInputAction: TextInputAction.search,
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: theme.colorScheme.secondaryContainer,
|
||||
border: OutlineInputBorder(
|
||||
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:convert';
|
||||
|
||||
import 'package:flutter/foundation.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/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/widgets/app_lock.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';
|
||||
|
||||
class HomeserverPicker extends StatefulWidget {
|
||||
const HomeserverPicker({super.key});
|
||||
final bool addMultiAccount;
|
||||
const HomeserverPicker({required this.addMultiAccount, super.key});
|
||||
|
||||
@override
|
||||
HomeserverPickerController createState() => HomeserverPickerController();
|
||||
|
|
@ -64,6 +63,21 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
|
||||
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
|
||||
/// 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
|
||||
|
|
@ -74,7 +88,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
if (homeserverController.text == _lastCheckedUrl) return;
|
||||
_lastCheckedUrl = homeserverController.text;
|
||||
setState(() {
|
||||
error = _rawLoginTypes = loginFlows = null;
|
||||
error = loginFlows = null;
|
||||
isLoading = true;
|
||||
});
|
||||
|
||||
|
|
@ -86,12 +100,6 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
final client = Matrix.of(context).getLoginClient();
|
||||
final (_, _, loginFlows) = await client.checkHomeserver(homeserver);
|
||||
this.loginFlows = loginFlows;
|
||||
if (supportsSso) {
|
||||
_rawLoginTypes = await client.request(
|
||||
RequestType.GET,
|
||||
'/client/v3/login',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
setState(() => error = (e).toLocalizedString(context));
|
||||
} finally {
|
||||
|
|
@ -113,9 +121,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
|
||||
bool get supportsPasswordLogin => _supportsFlow('m.login.password');
|
||||
|
||||
Map<String, dynamic>? _rawLoginTypes;
|
||||
|
||||
void ssoLoginAction(String? id) async {
|
||||
void ssoLoginAction() async {
|
||||
final redirectUrl = kIsWeb
|
||||
? Uri.parse(html.window.location.href)
|
||||
.resolveUri(
|
||||
|
|
@ -127,7 +133,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
: 'http://localhost:3001//login';
|
||||
|
||||
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},
|
||||
);
|
||||
|
||||
|
|
@ -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(
|
||||
'${GoRouter.of(context).routeInformationProvider.value.uri.path}/login',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:collection/collection.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/widgets/layouts/login_scaffold.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../../config/themes.dart';
|
||||
import '../../widgets/mxc_image.dart';
|
||||
import 'homeserver_app_bar.dart';
|
||||
import 'homeserver_picker.dart';
|
||||
|
||||
class HomeserverPickerView extends StatelessWidget {
|
||||
|
|
@ -21,22 +19,14 @@ class HomeserverPickerView extends StatelessWidget {
|
|||
Widget build(BuildContext 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(
|
||||
enforceMobileMode: Matrix.of(context).client.isLogged(),
|
||||
appBar: AppBar(
|
||||
titleSpacing: 12,
|
||||
automaticallyImplyLeading: false,
|
||||
surfaceTintColor: theme.colorScheme.surface,
|
||||
title: HomeserverAppBar(controller: controller),
|
||||
),
|
||||
appBar: controller.widget.addMultiAccount
|
||||
? AppBar(
|
||||
centerTitle: true,
|
||||
title: Text(L10n.of(context)!.addAccount),
|
||||
)
|
||||
: null,
|
||||
body: Column(
|
||||
children: [
|
||||
// display a prominent banner to import session for TOR browser
|
||||
|
|
@ -62,164 +52,133 @@ class HomeserverPickerView extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
Image.asset(
|
||||
'assets/banner_transparent.png',
|
||||
),
|
||||
Expanded(
|
||||
child: controller.isLoading
|
||||
? const Center(child: CircularProgressIndicator.adaptive())
|
||||
: ListView(
|
||||
children: [
|
||||
if (errorText != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
const Center(
|
||||
child: Icon(
|
||||
Icons.error_outline,
|
||||
size: 48,
|
||||
color: Colors.orange,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Center(
|
||||
child: Text(
|
||||
errorText,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.error,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Text(
|
||||
L10n.of(context)!
|
||||
.pleaseTryAgainLaterOrChooseDifferentServer,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.error,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 36),
|
||||
] else
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 0.0,
|
||||
right: 8.0,
|
||||
left: 8.0,
|
||||
bottom: 16.0,
|
||||
),
|
||||
child: Image.asset(
|
||||
'assets/banner_transparent.png',
|
||||
),
|
||||
),
|
||||
if (identityProviders != null) ...[
|
||||
...identityProviders.map(
|
||||
(provider) => _LoginButton(
|
||||
icon: provider.icon == null
|
||||
? const Icon(
|
||||
Icons.open_in_new_outlined,
|
||||
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(
|
||||
provider.name ??
|
||||
provider.brand ??
|
||||
L10n.of(context)!.singlesignon,
|
||||
),
|
||||
onPressed: () =>
|
||||
controller.ssoLoginAction(provider.id),
|
||||
),
|
||||
),
|
||||
],
|
||||
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,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 16.0,
|
||||
right: 8.0,
|
||||
left: 8.0,
|
||||
bottom: 16.0,
|
||||
),
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: TextField(
|
||||
onChanged: controller.tryCheckHomeserverActionWithCooldown,
|
||||
onEditingComplete:
|
||||
controller.tryCheckHomeserverActionWithoutCooldown,
|
||||
onSubmitted:
|
||||
controller.tryCheckHomeserverActionWithoutCooldown,
|
||||
onTap: controller.tryCheckHomeserverActionWithCooldown,
|
||||
controller: controller.homeserverController,
|
||||
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),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
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,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => launchUrl(
|
||||
Uri.https('servers.joinmatrix.org'),
|
||||
),
|
||||
child: Text(
|
||||
L10n.of(context)!.discoverHomeservers,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: Navigator.of(context).pop,
|
||||
child: Text(L10n.of(context)!.close),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.info_outlined),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (controller.supportsPasswordLogin || controller.supportsSso)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
child: 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)!.connect),
|
||||
),
|
||||
),
|
||||
if (controller.supportsPasswordLogin && controller.supportsSso)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: 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),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: 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),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LoginButton extends StatelessWidget {
|
||||
final Widget? icon;
|
||||
final String label;
|
||||
final void Function() onPressed;
|
||||
final bool withBorder;
|
||||
|
||||
const _LoginButton({
|
||||
this.icon,
|
||||
required this.label,
|
||||
required this.onPressed,
|
||||
this.withBorder = true,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
final icon = this.icon;
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
alignment: Alignment.center,
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton.icon(
|
||||
style: OutlinedButton.styleFrom(
|
||||
side: FluffyThemes.isColumnMode(context)
|
||||
? BorderSide.none
|
||||
: BorderSide(
|
||||
color: theme.colorScheme.outlineVariant,
|
||||
width: 1,
|
||||
),
|
||||
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:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/widgets/layouts/login_scaffold.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'login.dart';
|
||||
|
|
@ -24,11 +23,6 @@ class LoginView extends StatelessWidget {
|
|||
final title = L10n.of(context)!.logInTo(homeserver);
|
||||
final titleParts = title.split(homeserver);
|
||||
|
||||
final textFieldFillColor = FluffyThemes.isColumnMode(context)
|
||||
? theme.colorScheme.surface
|
||||
// ignore: deprecated_member_use
|
||||
: theme.colorScheme.surfaceVariant;
|
||||
|
||||
return LoginScaffold(
|
||||
enforceMobileMode: Matrix.of(context).client.isLogged(),
|
||||
appBar: AppBar(
|
||||
|
|
@ -73,8 +67,8 @@ class LoginView extends StatelessWidget {
|
|||
prefixIcon: const Icon(Icons.account_box_outlined),
|
||||
errorText: controller.usernameError,
|
||||
errorStyle: const TextStyle(color: Colors.orange),
|
||||
fillColor: textFieldFillColor,
|
||||
hintText: L10n.of(context)!.emailOrUsername,
|
||||
hintText: '@username:localpart',
|
||||
labelText: L10n.of(context)!.emailOrUsername,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -94,7 +88,6 @@ class LoginView extends StatelessWidget {
|
|||
prefixIcon: const Icon(Icons.lock_outlined),
|
||||
errorText: controller.passwordError,
|
||||
errorStyle: const TextStyle(color: Colors.orange),
|
||||
fillColor: textFieldFillColor,
|
||||
suffixIcon: IconButton(
|
||||
onPressed: controller.toggleShowPassword,
|
||||
icon: Icon(
|
||||
|
|
@ -104,21 +97,21 @@ class LoginView extends StatelessWidget {
|
|||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
hintText: L10n.of(context)!.password,
|
||||
hintText: '******',
|
||||
labelText: L10n.of(context)!.password,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: ElevatedButton.icon(
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: theme.colorScheme.primary,
|
||||
foregroundColor: theme.colorScheme.onPrimary,
|
||||
),
|
||||
onPressed: controller.loading ? null : controller.login,
|
||||
icon: const Icon(Icons.login_outlined),
|
||||
label: controller.loading
|
||||
child: controller.loading
|
||||
? const LinearProgressIndicator()
|
||||
: Text(L10n.of(context)!.login),
|
||||
),
|
||||
|
|
@ -126,15 +119,14 @@ class LoginView extends StatelessWidget {
|
|||
const SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: TextButton.icon(
|
||||
child: TextButton(
|
||||
onPressed: controller.loading
|
||||
? () {}
|
||||
: controller.passwordForgotten,
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: theme.colorScheme.error,
|
||||
),
|
||||
icon: const Icon(Icons.safety_check_outlined),
|
||||
label: Text(L10n.of(context)!.passwordForgotten),
|
||||
child: Text(L10n.of(context)!.passwordForgotten),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class LoginScaffold extends StatelessWidget {
|
|||
child: ConstrainedBox(
|
||||
constraints: isMobileMode
|
||||
? const BoxConstraints()
|
||||
: const BoxConstraints(maxWidth: 480, maxHeight: 720),
|
||||
: const BoxConstraints(maxWidth: 480, maxHeight: 640),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: 10.0,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue