diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index e4bcfcbd..dbc17507 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -376,6 +376,7 @@ "type": "text", "placeholders": {} }, + "benchmarkingHomeserver": "Finding the fastest instances around you...", "changeTheme": "Change your style", "@changeTheme": { "type": "text", diff --git a/config.sample.json b/config.sample.json index ff37ec23..553a3e6e 100644 --- a/config.sample.json +++ b/config.sample.json @@ -6,5 +6,6 @@ "privacy_url": "https://fluffychat.im/en/privacy.html", "render_html": false, "hide_redacted_events": false, - "hide_unknown_events": false + "hide_unknown_events": false, + "use_location_based_homeserver": false } \ No newline at end of file diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index de99da9b..4d507a93 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -4,10 +4,13 @@ import 'package:matrix/matrix.dart'; abstract class AppConfig { static String _applicationName = 'FluffyChat'; + static String get applicationName => _applicationName; static String? _applicationWelcomeMessage; + static String? get applicationWelcomeMessage => _applicationWelcomeMessage; static String _defaultHomeserver = 'matrix.org'; + static String get defaultHomeserver => _defaultHomeserver; static double bubbleSizeFactor = 1; static double fontSizeFactor = 1; @@ -21,12 +24,14 @@ abstract class AppConfig { static const Color secondaryColor = Color(0xFF41a2bc); static String _privacyUrl = 'https://gitlab.com/famedly/fluffychat/-/blob/main/PRIVACY.md'; + static String get privacyUrl => _privacyUrl; static const String enablePushTutorial = 'https://www.reddit.com/r/fluffychat/comments/qn6liu/enable_push_notifications_without_google_services/'; static const String appId = 'im.fluffychat.FluffyChat'; static const String appOpenUrlScheme = 'im.fluffychat'; static String _webBaseUrl = 'https://fluffychat.im/web'; + static String get webBaseUrl => _webBaseUrl; static const String sourceCodeUrl = 'https://gitlab.com/famedly/fluffychat'; static const String supportUrl = @@ -60,6 +65,7 @@ abstract class AppConfig { 'https://github.com/googlefonts/noto-emoji/'; static const double borderRadius = 16.0; static const double columnWidth = 360.0; + static bool useLocaleBasedHomeserver = false; static void loadFromJson(Map json) { if (json['chat_color'] != null) { @@ -95,5 +101,8 @@ abstract class AppConfig { if (json['hide_unknown_events'] is bool) { hideUnknownEvents = json['hide_unknown_events']; } + if (json['use_location_based_homeserver'] is bool) { + useLocaleBasedHomeserver = json['use_location_based_homeserver']; + } } } diff --git a/lib/config/homeserver_locale.dart b/lib/config/homeserver_locale.dart new file mode 100644 index 00000000..233143eb --- /dev/null +++ b/lib/config/homeserver_locale.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +import 'package:matrix/matrix.dart'; + +part 'homeserver_locale_map.dart'; + +class HomeserverLocale { + final Locale locale; + + const HomeserverLocale(this.locale); + + String? choose() { + if (locale.countryCode == null) return null; + final country = locale.countryCode!.toLowerCase(); + try { + if (_homeserverByLocale.containsKey(country)) { + return _homeserverByLocale[country]; + } else if (_countryCodeContinent.containsKey(country)) { + final continent = _countryCodeContinent[country]; + return _homeserverByContinent[continent]; + } + } catch (e) { + Logs().w('Error matching country code $country'); + } + return null; + } +} diff --git a/lib/config/homeserver_locale_map.dart b/lib/config/homeserver_locale_map.dart new file mode 100644 index 00000000..ff7e9aad --- /dev/null +++ b/lib/config/homeserver_locale_map.dart @@ -0,0 +1,281 @@ +part of 'homeserver_locale.dart'; + +final Map _homeserverByContinent = { + 'af': 'sykorp.com', + 'as': 'sibnsk.net', + 'an': 'buyvm.net', + 'aq': 'arcticfoxes.net', + 'eu': 'gemeinsam.jetzt', + 'sa': 'privex.io', + 'oc': 'mtrx.nz', +}; + +final Map _homeserverByLocale = { + 'ae': 'sykorp.com', + 'at': 'gemeinsam.jetzt', + 'ch': 'pragma-messenger.ch', + 'de': 'envs.de', + 'hu': 'grin.hu', + 'it': 'aria-net.org', + 'nl': 'nltrix.net', + 'nz': 'mtrx.nz', + 'ru': 'rumatrix.org', + 'us': 'buyvm.net', +}; + +final _countryCodeContinent = { + 'af': 'as', + 'al': 'eu', + 'aq': 'an', + 'dz': 'af', + 'as': 'oc', + 'ad': 'eu', + 'ao': 'af', + 'ag': 'na', + 'az': 'eu', + 'ar': 'sa', + 'au': 'oc', + 'at': 'eu', + 'bs': 'na', + 'bh': 'as', + 'bd': 'as', + 'am': 'eu', + 'bb': 'na', + 'be': 'eu', + 'bm': 'na', + 'bt': 'as', + 'bo': 'sa', + 'ba': 'eu', + 'bw': 'af', + 'bv': 'an', + 'br': 'sa', + 'bz': 'na', + 'io': 'as', + 'sb': 'oc', + 'vg': 'na', + 'bn': 'as', + 'bg': 'eu', + 'mm': 'as', + 'bi': 'af', + 'by': 'eu', + 'kh': 'as', + 'cm': 'af', + 'ca': 'na', + 'cv': 'af', + 'ky': 'na', + 'cf': 'af', + 'lk': 'as', + 'td': 'af', + 'cl': 'sa', + 'cn': 'as', + 'tw': 'as', + 'cx': 'as', + 'cc': 'as', + 'co': 'sa', + 'km': 'af', + 'yt': 'af', + 'cg': 'af', + 'cd': 'af', + 'ck': 'oc', + 'cr': 'na', + 'hr': 'eu', + 'cu': 'na', + 'cy': 'eu', + 'cz': 'eu', + 'bj': 'af', + 'dk': 'eu', + 'dm': 'na', + 'do': 'na', + 'ec': 'sa', + 'sv': 'na', + 'gq': 'af', + 'et': 'af', + 'er': 'af', + 'ee': 'eu', + 'fo': 'eu', + 'fk': 'sa', + 'gs': 'an', + 'fj': 'oc', + 'fi': 'eu', + 'ax': 'eu', + 'fr': 'eu', + 'gf': 'sa', + 'pf': 'oc', + 'tf': 'an', + 'dj': 'af', + 'ga': 'af', + 'ge': 'eu', + 'gm': 'af', + 'ps': 'as', + 'de': 'eu', + 'gh': 'af', + 'gi': 'eu', + 'ki': 'oc', + 'gr': 'eu', + 'gl': 'na', + 'gd': 'na', + 'gp': 'na', + 'gu': 'oc', + 'gt': 'na', + 'gn': 'af', + 'gy': 'sa', + 'ht': 'na', + 'hm': 'an', + 'va': 'eu', + 'hn': 'na', + 'hk': 'as', + 'hu': 'eu', + 'is': 'eu', + 'in': 'as', + 'id': 'as', + 'ir': 'as', + 'iq': 'as', + 'ie': 'eu', + 'il': 'as', + 'it': 'eu', + 'ci': 'af', + 'jm': 'na', + 'jp': 'as', + 'kz': 'eu', + 'jo': 'as', + 'ke': 'af', + 'kp': 'as', + 'kr': 'as', + 'kw': 'as', + 'kg': 'as', + 'la': 'as', + 'lb': 'as', + 'ls': 'af', + 'lv': 'eu', + 'lr': 'af', + 'ly': 'af', + 'li': 'eu', + 'lt': 'eu', + 'lu': 'eu', + 'mo': 'as', + 'mg': 'af', + 'mw': 'af', + 'my': 'as', + 'mv': 'as', + 'ml': 'af', + 'mt': 'eu', + 'mq': 'na', + 'mr': 'af', + 'mu': 'af', + 'mx': 'na', + 'mc': 'eu', + 'mn': 'as', + 'md': 'eu', + 'me': 'eu', + 'ms': 'na', + 'ma': 'af', + 'mz': 'af', + 'om': 'as', + 'na': 'af', + 'nr': 'oc', + 'np': 'as', + 'nl': 'eu', + 'an': 'na', + 'cw': 'na', + 'aw': 'na', + 'sx': 'na', + 'bq': 'na', + 'nc': 'oc', + 'vu': 'oc', + 'nz': 'oc', + 'ni': 'na', + 'ne': 'af', + 'ng': 'af', + 'nu': 'oc', + 'nf': 'oc', + 'no': 'eu', + 'mp': 'oc', + 'um': 'oc', + 'fm': 'oc', + 'mh': 'oc', + 'pw': 'oc', + 'pk': 'as', + 'pa': 'na', + 'pg': 'oc', + 'py': 'sa', + 'pe': 'sa', + 'ph': 'as', + 'pn': 'oc', + 'pl': 'eu', + 'pt': 'eu', + 'gw': 'af', + 'tl': 'as', + 'pr': 'na', + 'qa': 'as', + 're': 'af', + 'ro': 'eu', + 'ru': 'as', + 'rw': 'af', + 'bl': 'na', + 'sh': 'af', + 'kn': 'na', + 'ai': 'na', + 'lc': 'na', + 'mf': 'na', + 'pm': 'na', + 'vc': 'na', + 'sm': 'eu', + 'st': 'af', + 'sa': 'as', + 'sn': 'af', + 'rs': 'eu', + 'sc': 'af', + 'sl': 'af', + 'sg': 'as', + 'sk': 'eu', + 'vn': 'as', + 'si': 'eu', + 'so': 'af', + 'za': 'af', + 'zw': 'af', + 'es': 'eu', + 'ss': 'af', + 'eh': 'af', + 'sd': 'af', + 'sr': 'sa', + 'sj': 'eu', + 'sz': 'af', + 'se': 'eu', + 'ch': 'eu', + 'sy': 'as', + 'tj': 'as', + 'th': 'as', + 'tg': 'af', + 'tk': 'oc', + 'to': 'oc', + 'tt': 'na', + 'ae': 'as', + 'tn': 'af', + 'tr': 'as', + 'tm': 'as', + 'tc': 'na', + 'tv': 'oc', + 'ug': 'af', + 'ua': 'eu', + 'mk': 'eu', + 'eg': 'af', + 'gb': 'eu', + 'gg': 'eu', + 'je': 'eu', + 'im': 'eu', + 'tz': 'af', + 'us': 'na', + 'vi': 'na', + 'bf': 'af', + 'uy': 'sa', + 'uz': 'as', + 've': 'sa', + 'wf': 'oc', + 'ws': 'oc', + 'ye': 'as', + 'zm': 'af', + 'xx': 'oc', + 'xe': 'as', + 'xd': 'as', + 'xs': 'as', +}; diff --git a/lib/pages/homeserver_picker/homeserver_picker.dart b/lib/pages/homeserver_picker/homeserver_picker.dart index 1017e023..ea0ef4c3 100644 --- a/lib/pages/homeserver_picker/homeserver_picker.dart +++ b/lib/pages/homeserver_picker/homeserver_picker.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:ui'; import 'package:flutter/material.dart'; @@ -7,6 +8,7 @@ import 'package:matrix_homeserver_recommendations/matrix_homeserver_recommendati import 'package:vrouter/vrouter.dart'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/homeserver_locale.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'; @@ -28,6 +30,7 @@ class HomeserverPickerController extends State { String? error; List? benchmarkResults; bool displayServerList = false; + bool get loadingHomeservers => AppConfig.allowOtherHomeservers && benchmarkResults == null; String searchTerm = ''; @@ -129,6 +132,12 @@ class HomeserverPickerController extends State { } } + void _defaultHomeserverByLocale() { + final serverMatcher = HomeserverLocale(window.locale); + setState(() => homeserverController.text = + serverMatcher.choose() ?? AppConfig.defaultHomeserver); + } + @override void dispose() { homeserverFocusNode.removeListener(_updateFocus); @@ -138,6 +147,10 @@ class HomeserverPickerController extends State { @override void initState() { homeserverFocusNode.addListener(_updateFocus); + if (AppConfig.useLocaleBasedHomeserver) { + WidgetsBinding.instance + .addPostFrameCallback((timeStamp) => _defaultHomeserverByLocale()); + } super.initState(); } diff --git a/lib/pages/homeserver_picker/homeserver_picker_view.dart b/lib/pages/homeserver_picker/homeserver_picker_view.dart index 76f7e301..ec974e83 100644 --- a/lib/pages/homeserver_picker/homeserver_picker_view.dart +++ b/lib/pages/homeserver_picker/homeserver_picker_view.dart @@ -1,11 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:animations/animations.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:vrouter/vrouter.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pages/homeserver_picker/homeserver_tile.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/layouts/login_scaffold.dart'; import 'homeserver_picker.dart'; @@ -63,40 +65,58 @@ class HomeserverPickerView extends StatelessWidget { 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, - color: Colors.black, - ), - onPressed: () => - controller.showServerInfo(server), + child: PageTransitionSwitcher( + transitionBuilder: ( + Widget child, + Animation primaryAnimation, + Animation secondaryAnimation, + ) { + return SharedAxisTransition( + animation: primaryAnimation, + secondaryAnimation: secondaryAnimation, + transitionType: SharedAxisTransitionType.scaled, + child: child, + fillColor: Colors.transparent, + ); + }, + child: ListTileTheme( + data: const ListTileThemeData( + iconColor: Colors.black, + textColor: Colors.black, + ), + key: ValueKey(benchmarkResults), + child: benchmarkResults == null + ? Center( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const CircularProgressIndicator + .adaptive(), + ListTile( + leading: const Icon(Icons.rocket), + title: Text(L10n.of(context)! + .benchmarkingHomeserver), ), - onTap: () => controller.setServer( - server.homeserver.baseUrl.host), - title: Text( - server.homeserver.baseUrl.host, - style: const TextStyle( - color: Colors.black), - ), - subtitle: Text( - server.homeserver.description ?? '', - style: TextStyle( - color: Colors.grey.shade700), - ), - ), - ) - .toList(), - ), + ], + ), + )) + : ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: + controller.filteredHomeservers.length, + itemBuilder: (context, index) { + final server = + controller.filteredHomeservers[index]; + return HomeserverTile( + server: server, controller: controller); + }, + ), + ), + ), ), ), Wrap( diff --git a/lib/pages/homeserver_picker/homeserver_tile.dart b/lib/pages/homeserver_picker/homeserver_tile.dart new file mode 100644 index 00000000..4ace6bc5 --- /dev/null +++ b/lib/pages/homeserver_picker/homeserver_tile.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +import 'package:matrix_homeserver_recommendations/matrix_homeserver_recommendations.dart'; + +import 'package:fluffychat/pages/homeserver_picker/homeserver_picker.dart'; + +class HomeserverTile extends StatelessWidget { + final HomeserverBenchmarkResult server; + final HomeserverPickerController controller; + + const HomeserverTile( + {Key? key, required this.server, required this.controller}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return 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 ?? '', + style: TextStyle(color: Colors.grey.shade700), + ), + ); + } +}