feat: Onboarding with dynamic homeservers from joinmatrix.org

This commit is contained in:
Christian Pauly 2022-04-15 14:31:57 +02:00
parent e0fbd0ecb3
commit f6938f81fc
5 changed files with 196 additions and 9 deletions

View File

@ -0,0 +1,71 @@
import 'package:flutter/material.dart';
import 'package:matrix_homeserver_recommendations/matrix_homeserver_recommendations.dart';
import 'package:url_launcher/url_launcher.dart';
class HomeserverBottomSheet extends StatelessWidget {
final HomeserverBenchmarkResult homeserver;
const HomeserverBottomSheet({required this.homeserver, Key? key})
: super(key: key);
@override
Widget build(BuildContext context) {
final responseTime = homeserver.responseTime;
final description = homeserver.homeserver.description;
final rules = homeserver.homeserver.rules;
final privacy = homeserver.homeserver.privacyPolicy;
final registration = homeserver.homeserver.registration;
final jurisdiction = homeserver.homeserver.jurisdiction;
final homeserverSoftware = homeserver.homeserver.homeserverSoftware;
return Scaffold(
appBar: AppBar(
title: Text(homeserver.homeserver.baseUrl.host),
),
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: () => launch(homeserver.homeserver.baseUrl.toString()),
leading: const Icon(Icons.link_outlined),
title: Text(homeserver.homeserver.baseUrl.toString()),
),
if (registration != null)
ListTile(
onTap: () => launch(registration.toString()),
leading: const Icon(Icons.person_add_outlined),
title: Text(registration.toString()),
),
if (rules != null)
ListTile(
onTap: () => launch(rules.toString()),
leading: const Icon(Icons.visibility_outlined),
title: Text(rules.toString()),
),
if (privacy != null)
ListTile(
onTap: () => launch(privacy.toString()),
leading: const Icon(Icons.shield_outlined),
title: Text(privacy.toString()),
),
if (responseTime != null)
ListTile(
leading: const Icon(Icons.timer_outlined),
title: Text('${responseTime.inMilliseconds}ms'),
),
]),
);
}
}

View File

@ -3,9 +3,11 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:matrix_homeserver_recommendations/matrix_homeserver_recommendations.dart';
import 'package:vrouter/vrouter.dart'; import 'package:vrouter/vrouter.dart';
import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pages/homeserver_picker/homeserver_bottom_sheet.dart';
import 'package:fluffychat/pages/homeserver_picker/homeserver_picker_view.dart'; import 'package:fluffychat/pages/homeserver_picker/homeserver_picker_view.dart';
import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/matrix.dart';
import '../../utils/localized_exception_extension.dart'; import '../../utils/localized_exception_extension.dart';
@ -19,9 +21,62 @@ class HomeserverPicker extends StatefulWidget {
class HomeserverPickerController extends State<HomeserverPicker> { class HomeserverPickerController extends State<HomeserverPicker> {
bool isLoading = false; bool isLoading = false;
final TextEditingController homeserverController = final TextEditingController homeserverController = TextEditingController(
TextEditingController(text: AppConfig.defaultHomeserver); text: AppConfig.defaultHomeserver,
);
final FocusNode homeserverFocusNode = FocusNode();
String? error; String? error;
List<HomeserverBenchmarkResult>? benchmarkResults;
bool displayServerList = false;
bool get loadingHomeservers =>
AppConfig.allowOtherHomeservers && benchmarkResults == null;
String searchTerm = '';
void _updateFocus() {
if (benchmarkResults == null) _loadHomeserverList();
setState(() {
displayServerList = homeserverFocusNode.hasFocus;
});
}
void showServerInfo(HomeserverBenchmarkResult server) => showModalBottomSheet(
context: context,
builder: (_) => HomeserverBottomSheet(
homeserver: server,
),
);
void onChanged(String text) => setState(() {
searchTerm = text;
});
List<HomeserverBenchmarkResult> get filteredHomeservers => benchmarkResults!
.where((element) =>
element.homeserver.baseUrl.host.contains(searchTerm) ||
(element.homeserver.description?.contains(searchTerm) ?? false))
.toList();
void _loadHomeserverList() async {
try {
final homeserverList = await JoinmatrixOrgParser().fetchHomeservers();
final benchmark = await HomeserverListProvider.benchmarkHomeserver(
homeserverList,
timeout: const Duration(seconds: 10),
);
setState(() {
benchmarkResults = benchmark;
});
} catch (e, s) {
Logs().e('Homeserver benchmark failed', e, s);
benchmarkResults = [];
}
}
void setServer(String server) => setState(() {
homeserverController.text = server;
searchTerm = '';
homeserverFocusNode.unfocus();
});
/// 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
@ -29,8 +84,11 @@ class HomeserverPickerController extends State<HomeserverPicker> {
/// login type. /// login type.
Future<void> checkHomeserverAction() async { Future<void> checkHomeserverAction() async {
setState(() { setState(() {
homeserverFocusNode.unfocus();
error = null; error = null;
isLoading = true; isLoading = true;
searchTerm = '';
displayServerList = false;
}); });
try { try {
@ -68,6 +126,18 @@ class HomeserverPickerController extends State<HomeserverPicker> {
} }
} }
@override
void dispose() {
homeserverFocusNode.removeListener(_updateFocus);
super.dispose();
}
@override
void initState() {
homeserverFocusNode.addListener(_updateFocus);
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Matrix.of(context).navigatorContext = context; Matrix.of(context).navigatorContext = context;

View File

@ -17,6 +17,7 @@ class HomeserverPickerView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final benchmarkResults = controller.benchmarkResults;
return LoginScaffold( return LoginScaffold(
appBar: VRouter.of(context).path == '/home' appBar: VRouter.of(context).path == '/home'
? null ? null
@ -26,18 +27,19 @@ class HomeserverPickerView extends StatelessWidget {
Expanded( Expanded(
child: ListView( child: ListView(
children: [ children: [
Center( AnimatedContainer(
child: ConstrainedBox( duration: const Duration(milliseconds: 300),
constraints: const BoxConstraints(maxHeight: 256), constraints: BoxConstraints(
child: Image.asset( maxHeight: controller.displayServerList ? 0 : 256),
'assets/info-logo.png', alignment: Alignment.center,
), child: Image.asset('assets/info-logo.png'),
),
), ),
Padding( Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: TextField( child: TextField(
focusNode: controller.homeserverFocusNode,
controller: controller.homeserverController, controller: controller.homeserverController,
onChanged: controller.onChanged,
decoration: FluffyThemes.loginTextFieldDecoration( decoration: FluffyThemes.loginTextFieldDecoration(
labelText: L10n.of(context)!.homeserver, labelText: L10n.of(context)!.homeserver,
hintText: L10n.of(context)!.enterYourHomeserver, hintText: L10n.of(context)!.enterYourHomeserver,
@ -49,6 +51,42 @@ class HomeserverPickerView extends StatelessWidget {
autocorrect: false, autocorrect: false,
), ),
), ),
if (controller.displayServerList)
Padding(
padding: const EdgeInsets.all(16.0),
child: Material(
borderRadius:
BorderRadius.circular(AppConfig.borderRadius),
color: Colors.white.withAlpha(200),
clipBehavior: Clip.hardEdge,
child: benchmarkResults == null
? const Center(
child: Padding(
padding: EdgeInsets.all(16.0),
child: CircularProgressIndicator.adaptive(),
))
: Column(
children: controller.filteredHomeservers
.map(
(server) => ListTile(
trailing: IconButton(
icon: const Icon(Icons.info_outlined),
onPressed: () =>
controller.showServerInfo(server),
),
onTap: () => controller.setServer(
server.homeserver.baseUrl.host),
title: Text(
server.homeserver.baseUrl.host,
),
subtitle: Text(
server.homeserver.description ?? ''),
),
)
.toList(),
),
),
),
Wrap( Wrap(
alignment: WrapAlignment.center, alignment: WrapAlignment.center,
children: [ children: [

View File

@ -1019,6 +1019,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.5.3" version: "0.5.3"
matrix_homeserver_recommendations:
dependency: "direct main"
description:
name: matrix_homeserver_recommendations
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0"
matrix_link_text: matrix_link_text:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -58,6 +58,7 @@ dependencies:
localstorage: ^4.0.0+1 localstorage: ^4.0.0+1
lottie: ^1.2.2 lottie: ^1.2.2
matrix: ^0.8.20 matrix: ^0.8.20
matrix_homeserver_recommendations: ^0.2.0
matrix_link_text: ^1.0.2 matrix_link_text: ^1.0.2
native_imaging: native_imaging:
git: https://gitlab.com/famedly/company/frontend/libraries/native_imaging.git git: https://gitlab.com/famedly/company/frontend/libraries/native_imaging.git