mirror of
https://gitlab.com/famedly/fluffychat.git
synced 2024-11-30 16:29:30 +01:00
feat: propose homeserver based on response time
- use `package:matrix_homeserver_recommendations` to benchmark homeservers - propose fastest server without anti-features as homeserver - display small button with server information - use Matrix.org / the default configuration as fallback Signed-off-by: TheOneWithTheBraid <the-one@with-the-braid.cf>
This commit is contained in:
parent
4b17b1651b
commit
1e194175da
@ -2774,5 +2774,16 @@
|
||||
"widgetName": "Name",
|
||||
"widgetUrlError": "This is not a valid URL.",
|
||||
"widgetNameError": "Please provide a display name.",
|
||||
"errorAddingWidget": "Error adding the widget."
|
||||
"errorAddingWidget": "Error adding the widget.",
|
||||
"reportUser": "Report user",
|
||||
"showAvailableHomeservers": "Show available homeservers (Advanced users)",
|
||||
"antiFeatures": "Anti-features",
|
||||
"noAntiFeaturesRecorded": "No anti features recorded",
|
||||
"serverRules": "Server rules",
|
||||
"jurisdiction": "Jurisdiction",
|
||||
"selectServer": "Select server",
|
||||
"responseTime": "Response time",
|
||||
"openServerList": "Visit",
|
||||
"reportServerListProblem": "Report list issue",
|
||||
"serverListJoinMatrix": "Server list by joinMatrix.org"
|
||||
}
|
||||
|
@ -3,10 +3,12 @@ import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:flutter_web_auth/flutter_web_auth.dart';
|
||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:matrix_homeserver_recommendations/matrix_homeserver_recommendations.dart';
|
||||
import 'package:universal_html/html.dart' as html;
|
||||
import 'package:vrouter/vrouter.dart';
|
||||
|
||||
@ -16,6 +18,7 @@ import 'package:fluffychat/utils/famedlysdk_store.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../../utils/localized_exception_extension.dart';
|
||||
import 'homeserver_tile.dart';
|
||||
|
||||
class HomeserverPicker extends StatefulWidget {
|
||||
const HomeserverPicker({Key? key}) : super(key: key);
|
||||
@ -25,14 +28,19 @@ class HomeserverPicker extends StatefulWidget {
|
||||
}
|
||||
|
||||
class HomeserverPickerController extends State<HomeserverPicker> {
|
||||
bool isLoading = false;
|
||||
String domain = AppConfig.defaultHomeserver;
|
||||
final TextEditingController homeserverController =
|
||||
TextEditingController(text: AppConfig.defaultHomeserver);
|
||||
bool isLoading = true;
|
||||
|
||||
String? domain;
|
||||
List<HomeserverBenchmarkResult>? benchmarkResults;
|
||||
|
||||
TextEditingController? homeserverController;
|
||||
|
||||
StreamSubscription? _intentDataStreamSubscription;
|
||||
String? error;
|
||||
Timer? _coolDown;
|
||||
|
||||
late HomeserverListProvider parser;
|
||||
|
||||
void setDomain(String domain) {
|
||||
this.domain = domain;
|
||||
_coolDown?.cancel();
|
||||
@ -67,6 +75,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
checkHomeserverAction();
|
||||
benchmarkHomeServers();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -84,10 +93,12 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
||||
Future<void> checkHomeserverAction() async {
|
||||
_coolDown?.cancel();
|
||||
if (_lastCheckedHomeserver == domain) return;
|
||||
if (domain.isEmpty) throw L10n.of(context)!.changeTheHomeserver;
|
||||
if (domain == null || domain!.isEmpty) {
|
||||
throw L10n.of(context)!.changeTheHomeserver;
|
||||
}
|
||||
var homeserver = domain;
|
||||
|
||||
if (!homeserver.startsWith('https://')) {
|
||||
if (!homeserver!.startsWith('https://')) {
|
||||
homeserver = 'https://$homeserver';
|
||||
}
|
||||
|
||||
@ -179,7 +190,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
||||
|
||||
void signUpAction() => VRouter.of(context).to(
|
||||
'signup',
|
||||
queryParameters: {'domain': domain},
|
||||
queryParameters: {'domain': domain!},
|
||||
);
|
||||
|
||||
@override
|
||||
@ -187,6 +198,75 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
||||
Matrix.of(context).navigatorContext = context;
|
||||
return HomeserverPickerView(this);
|
||||
}
|
||||
|
||||
Future<void> benchmarkHomeServers() async {
|
||||
try {
|
||||
parser = JoinmatrixOrgParser();
|
||||
final homeserverList = await parser.fetchHomeservers();
|
||||
final benchmark = await HomeserverListProvider.benchmarkHomeserver(
|
||||
homeserverList,
|
||||
timeout: const Duration(seconds: 10),
|
||||
// TODO: do not rely on the homeserver list information telling the server supports registration
|
||||
);
|
||||
|
||||
if (benchmark.isEmpty) {
|
||||
throw NullThrownError();
|
||||
}
|
||||
|
||||
// trying to use server without anti-features
|
||||
final goodServers = <HomeserverBenchmarkResult>[];
|
||||
final badServers = <HomeserverBenchmarkResult>[];
|
||||
for (final result in benchmark) {
|
||||
if (result.homeserver.antiFeatures == null) {
|
||||
goodServers.add(result);
|
||||
} else {
|
||||
badServers.add(result);
|
||||
}
|
||||
}
|
||||
|
||||
goodServers.sort();
|
||||
badServers.sort();
|
||||
|
||||
benchmarkResults = List.from([...goodServers, ...badServers]);
|
||||
|
||||
domain = benchmarkResults!.first.homeserver.baseUrl.host;
|
||||
} on Exception catch (e, s) {
|
||||
Logs().e('Homeserver benchmark failed', e, s);
|
||||
domain = AppConfig.defaultHomeserver;
|
||||
} finally {
|
||||
homeserverController = TextEditingController(text: domain);
|
||||
checkHomeserverAction();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> showServerPicker() async {
|
||||
final selection = await showModal(
|
||||
context: context,
|
||||
builder: (context) => SimpleDialog(
|
||||
title: Text(L10n.of(context)!.changeTheHomeserver),
|
||||
children: [
|
||||
...benchmarkResults!.map<Widget>(
|
||||
(e) => HomeserverTile(
|
||||
benchmark: e,
|
||||
onSelect: () {
|
||||
Navigator.of(context).pop(e.homeserver);
|
||||
},
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
JoinMatrixAttributionTile(),
|
||||
]),
|
||||
);
|
||||
if (selection is Homeserver) {
|
||||
if (domain != selection.baseUrl.host) {
|
||||
setState(() {
|
||||
domain = selection.baseUrl.host;
|
||||
homeserverController!.text = domain!;
|
||||
});
|
||||
checkHomeserverAction();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IdentityProvider {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
@ -25,18 +26,44 @@ class HomeserverPickerView extends StatelessWidget {
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
titleSpacing: 8,
|
||||
title: DefaultAppBarSearchField(
|
||||
prefixText: 'https://',
|
||||
hintText: L10n.of(context)!.enterYourHomeserver,
|
||||
searchController: controller.homeserverController,
|
||||
suffix: const Icon(Icons.edit_outlined),
|
||||
padding: EdgeInsets.zero,
|
||||
onChanged: controller.setDomain,
|
||||
readOnly: !AppConfig.allowOtherHomeservers,
|
||||
onSubmit: (_) => controller.checkHomeserverAction(),
|
||||
unfocusOnClear: false,
|
||||
autocorrect: false,
|
||||
labelText: L10n.of(context)!.homeserver,
|
||||
title: PageTransitionSwitcher(
|
||||
transitionBuilder: (
|
||||
Widget child,
|
||||
Animation<double> primaryAnimation,
|
||||
Animation<double> secondaryAnimation,
|
||||
) {
|
||||
return SharedAxisTransition(
|
||||
animation: primaryAnimation,
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
transitionType: SharedAxisTransitionType.vertical,
|
||||
fillColor: Colors.transparent,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: controller.homeserverController == null
|
||||
? Center(
|
||||
key: ValueKey(controller.homeserverController),
|
||||
child: const CircularProgressIndicator(),
|
||||
)
|
||||
: DefaultAppBarSearchField(
|
||||
key: ValueKey(controller.homeserverController),
|
||||
prefixIcon: IconButton(
|
||||
icon: const Icon(Icons.format_list_numbered),
|
||||
onPressed: controller.showServerPicker,
|
||||
tooltip: L10n.of(context)!.showAvailableHomeservers,
|
||||
),
|
||||
prefixText: 'https://',
|
||||
hintText: L10n.of(context)!.enterYourHomeserver,
|
||||
searchController: controller.homeserverController,
|
||||
suffix: const Icon(Icons.edit_outlined),
|
||||
padding: EdgeInsets.zero,
|
||||
onChanged: controller.setDomain,
|
||||
readOnly: !AppConfig.allowOtherHomeservers,
|
||||
onSubmit: (_) => controller.checkHomeserverAction(),
|
||||
unfocusOnClear: false,
|
||||
autocorrect: false,
|
||||
labelText: L10n.of(context)!.homeserver,
|
||||
),
|
||||
),
|
||||
elevation: 0,
|
||||
),
|
||||
|
108
lib/pages/homeserver_picker/homeserver_tile.dart
Normal file
108
lib/pages/homeserver_picker/homeserver_tile.dart
Normal file
@ -0,0 +1,108 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix_homeserver_recommendations/matrix_homeserver_recommendations.dart';
|
||||
import 'package:url_launcher/link.dart';
|
||||
|
||||
class HomeserverTile extends StatelessWidget {
|
||||
final HomeserverBenchmarkResult benchmark;
|
||||
final VoidCallback onSelect;
|
||||
|
||||
const HomeserverTile(
|
||||
{Key? key, required this.benchmark, required this.onSelect})
|
||||
: super(key: key);
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ExpansionTile(
|
||||
title: Text(benchmark.homeserver.baseUrl.host),
|
||||
subtitle: benchmark.homeserver.description != null
|
||||
? Text(benchmark.homeserver.description!)
|
||||
: null,
|
||||
children: [
|
||||
benchmark.homeserver.antiFeatures != null &&
|
||||
benchmark.homeserver.antiFeatures!.isNotEmpty
|
||||
? ListTile(
|
||||
leading: const Icon(Icons.thumb_down),
|
||||
title: Text(benchmark.homeserver.antiFeatures!),
|
||||
subtitle: Text(L10n.of(context)!.antiFeatures),
|
||||
)
|
||||
: ListTile(
|
||||
leading: const Icon(Icons.recommend),
|
||||
title: Text(L10n.of(context)!.noAntiFeaturesRecorded),
|
||||
),
|
||||
if (benchmark.homeserver.jurisdiction != null)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.public),
|
||||
title: Text(benchmark.homeserver.jurisdiction!),
|
||||
subtitle: Text(L10n.of(context)!.jurisdiction),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.speed),
|
||||
title: Text("${benchmark.responseTime!.inMilliseconds} ms"),
|
||||
subtitle: Text(L10n.of(context)!.responseTime),
|
||||
),
|
||||
ButtonBar(
|
||||
/* spacing: 8,
|
||||
runSpacing: 8,
|
||||
alignment: WrapAlignment.end,
|
||||
runAlignment: WrapAlignment.center,
|
||||
crossAxisAlignment: WrapCrossAlignment.center, */
|
||||
children: [
|
||||
if (benchmark.homeserver.privacyPolicy != null)
|
||||
Link(
|
||||
uri: benchmark.homeserver.privacyPolicy!,
|
||||
target: LinkTarget.blank,
|
||||
builder: (context, callback) => TextButton(
|
||||
onPressed: callback,
|
||||
child: Text(L10n.of(context)!.privacy),
|
||||
),
|
||||
),
|
||||
if (benchmark.homeserver.rules != null)
|
||||
Link(
|
||||
uri: benchmark.homeserver.rules!,
|
||||
target: LinkTarget.blank,
|
||||
builder: (context, callback) => TextButton(
|
||||
onPressed: callback,
|
||||
child: Text(L10n.of(context)!.serverRules),
|
||||
),
|
||||
),
|
||||
OutlinedButton(
|
||||
onPressed: onSelect.call,
|
||||
child: Text(L10n.of(context)!.selectServer),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class JoinMatrixAttributionTile extends StatelessWidget {
|
||||
final parser = JoinmatrixOrgParser();
|
||||
|
||||
JoinMatrixAttributionTile({Key? key}) : super(key: key);
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
title: Text(L10n.of(context)!.serverListJoinMatrix),
|
||||
subtitle: ButtonBar(children: [
|
||||
Link(
|
||||
uri: parser.externalUri,
|
||||
target: LinkTarget.blank,
|
||||
builder: (context, callback) => TextButton(
|
||||
onPressed: callback,
|
||||
child: Text(L10n.of(context)!.openServerList),
|
||||
),
|
||||
),
|
||||
Link(
|
||||
uri: parser.errorReportUrl,
|
||||
target: LinkTarget.blank,
|
||||
builder: (context, callback) => TextButton(
|
||||
onPressed: callback,
|
||||
child: Text(L10n.of(context)!.reportServerListProblem),
|
||||
),
|
||||
),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
23
pubspec.lock
23
pubspec.lock
@ -991,6 +991,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:
|
||||
@ -1697,7 +1704,21 @@ packages:
|
||||
name: unifiedpush
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "4.0.0"
|
||||
unifiedpush_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: unifiedpush_android
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
unifiedpush_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: unifiedpush_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
universal_html:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -59,6 +59,7 @@ dependencies:
|
||||
localstorage: ^4.0.0+1
|
||||
lottie: ^1.2.2
|
||||
matrix: ^0.8.17
|
||||
matrix_homeserver_recommendations: ^0.2.0
|
||||
matrix_link_text: ^1.0.2
|
||||
native_imaging:
|
||||
git: https://gitlab.com/famedly/libraries/native_imaging.git
|
||||
|
Loading…
Reference in New Issue
Block a user