Merge branch 'krille/joinmatrix-org-homeserver-list' into 'main'

feat: Onboarding with dynamic homeservers from joinmatrix.org

See merge request famedly/fluffychat!830
This commit is contained in:
Krille Fear 2022-04-16 06:01:31 +00:00
commit dae25018de
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:matrix/matrix.dart';
import 'package:matrix_homeserver_recommendations/matrix_homeserver_recommendations.dart';
import 'package:vrouter/vrouter.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/widgets/matrix.dart';
import '../../utils/localized_exception_extension.dart';
@ -19,9 +21,62 @@ class HomeserverPicker extends StatefulWidget {
class HomeserverPickerController extends State<HomeserverPicker> {
bool isLoading = false;
final TextEditingController homeserverController =
TextEditingController(text: AppConfig.defaultHomeserver);
final TextEditingController homeserverController = TextEditingController(
text: AppConfig.defaultHomeserver,
);
final FocusNode homeserverFocusNode = FocusNode();
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
/// makes sure that it is prefixed with https. Then it searches for the
@ -29,8 +84,11 @@ class HomeserverPickerController extends State<HomeserverPicker> {
/// login type.
Future<void> checkHomeserverAction() async {
setState(() {
homeserverFocusNode.unfocus();
error = null;
isLoading = true;
searchTerm = '';
displayServerList = false;
});
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
Widget build(BuildContext context) {
Matrix.of(context).navigatorContext = context;

View File

@ -17,6 +17,7 @@ class HomeserverPickerView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final benchmarkResults = controller.benchmarkResults;
return LoginScaffold(
appBar: VRouter.of(context).path == '/home'
? null
@ -26,18 +27,19 @@ class HomeserverPickerView extends StatelessWidget {
Expanded(
child: ListView(
children: [
Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 256),
child: Image.asset(
'assets/info-logo.png',
),
),
AnimatedContainer(
duration: const Duration(milliseconds: 300),
constraints: BoxConstraints(
maxHeight: controller.displayServerList ? 0 : 256),
alignment: Alignment.center,
child: Image.asset('assets/info-logo.png'),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
focusNode: controller.homeserverFocusNode,
controller: controller.homeserverController,
onChanged: controller.onChanged,
decoration: FluffyThemes.loginTextFieldDecoration(
labelText: L10n.of(context)!.homeserver,
hintText: L10n.of(context)!.enterYourHomeserver,
@ -49,6 +51,42 @@ class HomeserverPickerView extends StatelessWidget {
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(
alignment: WrapAlignment.center,
children: [

View File

@ -1019,6 +1019,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
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:
dependency: "direct main"
description:

View File

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