From 75800ccdd5263707f73d8a2bc37f93b2a8b7ce31 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sun, 25 Aug 2024 08:59:35 +0200 Subject: [PATCH 1/8] chore: Follow up send file dialog fix --- lib/pages/chat/send_file_dialog.dart | 34 ++++++++++++++++------------ 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/lib/pages/chat/send_file_dialog.dart b/lib/pages/chat/send_file_dialog.dart index 79b4eed83..edcb981f3 100644 --- a/lib/pages/chat/send_file_dialog.dart +++ b/lib/pages/chat/send_file_dialog.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -33,6 +31,8 @@ class SendFileDialogState extends State { static const int minSizeToCompress = 20 * 1024; Future _send() async { + final scaffoldMessenger = ScaffoldMessenger.of(context); + final l10n = L10n.of(context)!; for (var file in widget.files) { MatrixImageFile? thumbnail; if (file is MatrixVideoFile && file.bytes.length > minSizeToCompress) { @@ -44,20 +44,24 @@ class SendFileDialogState extends State { }, ); } - try { - await widget.room.sendFileEvent( - file, - thumbnail: thumbnail, - shrinkImageMaxDimension: origImage ? null : 1600, - ); - } on IOException catch (_) { - } on FileTooBigMatrixException catch (_) { - } catch (e, s) { - if (mounted) { + widget.room + .sendFileEvent( + file, + thumbnail: thumbnail, + shrinkImageMaxDimension: origImage ? null : 1600, + ) + .catchError( + (e, s) { + if (e is FileTooBigMatrixException) { + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(l10n.fileIsTooBigForServer)), + ); + return null; + } ErrorReporter(context, 'Unable to send file').onErrorCallback(e, s); - } - rethrow; - } + return null; + }, + ); } Navigator.of(context, rootNavigator: false).pop(); From f17b09f56c20e2776daaa3cab1b7af9fa7ee240e Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sun, 25 Aug 2024 09:02:51 +0200 Subject: [PATCH 2/8] chore: Follow up set read marker --- lib/pages/chat/chat.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index a373b87ea..7772e5f65 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -300,6 +300,9 @@ class ChatController extends State _showScrollUpMaterialBanner(readMarkerEventId); } + // Mark room as read on first visit if requirements are fulfilled + setReadMarker(); + if (!mounted) return; } catch (e, s) { ErrorReporter(context, 'Unable to load timeline').onErrorCallback(e, s); From dec588d0c0855a8c2a45d132e6f410f449b0a24c Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sat, 17 Aug 2024 12:16:55 +0200 Subject: [PATCH 3/8] refactor: Simplify login UX --- assets/l10n/intl_en.arb | 6 +- lib/config/routes.dart | 4 +- lib/config/themes.dart | 15 +- lib/pages/bootstrap/bootstrap_dialog.dart | 4 +- lib/pages/chat_list/chat_list_header.dart | 1 + lib/pages/chat_list/space_view.dart | 1 + .../homeserver_picker/homeserver_app_bar.dart | 109 ------- .../homeserver_bottom_sheet.dart | 53 ---- .../homeserver_picker/homeserver_picker.dart | 67 ++-- .../homeserver_picker_view.dart | 299 ++++++++---------- .../homeserver_picker/public_homeserver.dart | 73 ----- lib/pages/login/login_view.dart | 24 +- lib/widgets/layouts/login_scaffold.dart | 2 +- pubspec.lock | 4 +- pubspec.yaml | 2 +- 15 files changed, 175 insertions(+), 489 deletions(-) delete mode 100644 lib/pages/homeserver_picker/homeserver_app_bar.dart delete mode 100644 lib/pages/homeserver_picker/homeserver_bottom_sheet.dart delete mode 100644 lib/pages/homeserver_picker/public_homeserver.dart diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index aa2d05420..e9146f2cf 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -2756,5 +2756,9 @@ } }, "changelog": "Changelog", - "sendCanceled": "Sending canceled" + "sendCanceled": "Sending canceled", + "loginWithMatrixId": "Login with Matrix-ID", + "discoverHomeservers": "Discover homeservers", + "whatIsAHomeserver": "What is a homeserver?", + "homeserverDescription": "All your data is stored on the homeserver, just like an email provider. You can choose which homeserver you want to use, while you can still communicate with everyone. Learn more at at https://matrix.org." } diff --git a/lib/config/routes.dart b/lib/config/routes.dart index d11cd56db..b839dd3f3 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -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( diff --git a/lib/config/themes.dart b/lib/config/themes.dart index f83b69a2b..4fcff7b75 100644 --- a/lib/config/themes.dart +++ b/lib/config/themes.dart @@ -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), - ), ), ), ); diff --git a/lib/pages/bootstrap/bootstrap_dialog.dart b/lib/pages/bootstrap/bootstrap_dialog.dart index 64e7febad..b816d2fc1 100644 --- a/lib/pages/bootstrap/bootstrap_dialog.dart +++ b/lib/pages/bootstrap/bootstrap_dialog.dart @@ -264,7 +264,9 @@ class BootstrapDialogState extends State { 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, ), diff --git a/lib/pages/chat_list/chat_list_header.dart b/lib/pages/chat_list/chat_list_header.dart index 864a7a7f8..f42999042 100644 --- a/lib/pages/chat_list/chat_list_header.dart +++ b/lib/pages/chat_list/chat_list_header.dart @@ -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, diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index 0295d2b55..69d9c9696 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -361,6 +361,7 @@ class _SpaceViewState extends State { onChanged: (_) => setState(() {}), textInputAction: TextInputAction.search, decoration: InputDecoration( + filled: true, fillColor: theme.colorScheme.secondaryContainer, border: OutlineInputBorder( borderSide: BorderSide.none, diff --git a/lib/pages/homeserver_picker/homeserver_app_bar.dart b/lib/pages/homeserver_picker/homeserver_app_bar.dart deleted file mode 100644 index 3ba506454..000000000 --- a/lib/pages/homeserver_picker/homeserver_app_bar.dart +++ /dev/null @@ -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( - 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, - ), - ); - } -} diff --git a/lib/pages/homeserver_picker/homeserver_bottom_sheet.dart b/lib/pages/homeserver_picker/homeserver_bottom_sheet.dart deleted file mode 100644 index 18256d6fb..000000000 --- a/lib/pages/homeserver_picker/homeserver_bottom_sheet.dart +++ /dev/null @@ -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), - ), - ], - ), - ); - } -} diff --git a/lib/pages/homeserver_picker/homeserver_picker.dart b/lib/pages/homeserver_picker/homeserver_picker.dart index 7ffe089a2..b2f782f34 100644 --- a/lib/pages/homeserver_picker/homeserver_picker.dart +++ b/lib/pages/homeserver_picker/homeserver_picker.dart @@ -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 { 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 { 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 { 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 { bool get supportsPasswordLogin => _supportsFlow('m.login.password'); - Map? _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 { : '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 { } } - List? 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? cachedHomeservers; - - Future> 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', ); diff --git a/lib/pages/homeserver_picker/homeserver_picker_view.dart b/lib/pages/homeserver_picker/homeserver_picker_view.dart index dd6c50d04..3fd7bd24c 100644 --- a/lib/pages/homeserver_picker/homeserver_picker_view.dart +++ b/lib/pages/homeserver_picker/homeserver_picker_view.dart @@ -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(), - ), - ), - ); - } -} diff --git a/lib/pages/homeserver_picker/public_homeserver.dart b/lib/pages/homeserver_picker/public_homeserver.dart deleted file mode 100644 index 4eef2aa56..000000000 --- a/lib/pages/homeserver_picker/public_homeserver.dart +++ /dev/null @@ -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? languages; - final List? 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 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.from(json['languages']), - features: json['features'] == null - ? null - : List.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'], - ); -} diff --git a/lib/pages/login/login_view.dart b/lib/pages/login/login_view.dart index 0358c44a6..a8ec3f502 100644 --- a/lib/pages/login/login_view.dart +++ b/lib/pages/login/login_view.dart @@ -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), diff --git a/lib/widgets/layouts/login_scaffold.dart b/lib/widgets/layouts/login_scaffold.dart index 7337e8ebd..08ed8d75c 100644 --- a/lib/widgets/layouts/login_scaffold.dart +++ b/lib/widgets/layouts/login_scaffold.dart @@ -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, diff --git a/pubspec.lock b/pubspec.lock index 98aa4393a..ede4347e4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2351,10 +2351,10 @@ packages: dependency: "direct overridden" description: name: win32 - sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" + sha256: "015002c060f1ae9f41a818f2d5640389cc05283e368be19dc8d77cecb43c40c9" url: "https://pub.dev" source: hosted - version: "5.5.0" + version: "5.5.3" win32_registry: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 853df1744..d338f9a75 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -160,4 +160,4 @@ dependency_overrides: git: url: https://github.com/TheOneWithTheBraid/keyboard_shortcuts.git ref: null-safety - win32: 5.5.0 + win32: 5.5.3 From 158a6855c3f6b6aa415241df8659f5d402fe4f39 Mon Sep 17 00:00:00 2001 From: Krille Date: Tue, 20 Aug 2024 09:27:00 +0200 Subject: [PATCH 4/8] feat: Use matrix authenticated media --- .../settings_emotes/settings_emotes.dart | 7 ++- .../client_download_content_extension.dart | 53 ++++++++++++++++++ lib/utils/push_helper.dart | 52 ++++++++++-------- .../local_notifications_extension.dart | 55 +++++++------------ lib/widgets/mxc_image.dart | 48 +++------------- pubspec.lock | 27 ++++----- pubspec.yaml | 5 +- 7 files changed, 133 insertions(+), 114 deletions(-) create mode 100644 lib/utils/client_download_content_extension.dart diff --git a/lib/pages/settings_emotes/settings_emotes.dart b/lib/pages/settings_emotes/settings_emotes.dart index 2ef905b17..fbef5479c 100644 --- a/lib/pages/settings_emotes/settings_emotes.dart +++ b/lib/pages/settings_emotes/settings_emotes.dart @@ -330,8 +330,11 @@ class EmotesSettingsController extends State { for (final entry in pack.images.entries) { final emote = entry.value; final name = entry.key; - final url = emote.url.getDownloadLink(client); - final response = await get(url); + final url = await emote.url.getDownloadUri(client); + final response = await get( + url, + headers: {'authorization': 'Bearer ${client.accessToken}'}, + ); archive.addFile( ArchiveFile( diff --git a/lib/utils/client_download_content_extension.dart b/lib/utils/client_download_content_extension.dart new file mode 100644 index 000000000..9fff1eccd --- /dev/null +++ b/lib/utils/client_download_content_extension.dart @@ -0,0 +1,53 @@ +import 'dart:typed_data'; + +import 'package:matrix/matrix.dart'; + +extension ClientDownloadContentExtension on Client { + Future downloadMxcCached( + Uri mxc, { + num? width, + num? height, + bool isThumbnail = false, + bool? animated, + ThumbnailMethod? thumbnailMethod, + }) async { + // To stay compatible with previous storeKeys: + final cacheKey = isThumbnail + // ignore: deprecated_member_use + ? mxc.getThumbnail( + this, + width: width, + height: height, + animated: animated, + method: thumbnailMethod!, + ) + : mxc; + + final cachedData = await database?.getFile(cacheKey); + if (cachedData != null) return cachedData; + + final httpUri = isThumbnail + ? await mxc.getThumbnailUri( + this, + width: width, + height: height, + animated: animated, + method: thumbnailMethod, + ) + : await mxc.getDownloadUri(this); + + final response = await httpClient.get( + httpUri, + headers: + accessToken == null ? null : {'authorization': 'Bearer $accessToken'}, + ); + if (response.statusCode != 200) { + throw Exception(); + } + final remoteData = response.bodyBytes; + + await database?.storeFile(cacheKey, remoteData, 0); + + return remoteData; + } +} diff --git a/lib/utils/push_helper.dart b/lib/utils/push_helper.dart index edfdb4941..7a82b370f 100644 --- a/lib/utils/push_helper.dart +++ b/lib/utils/push_helper.dart @@ -1,11 +1,9 @@ -import 'dart:io'; import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; -import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_shortcuts/flutter_shortcuts.dart'; @@ -13,6 +11,7 @@ import 'package:matrix/matrix.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/utils/client_download_content_extension.dart'; import 'package:fluffychat/utils/client_manager.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/platform_infos.dart'; @@ -177,28 +176,25 @@ Future _tryPushHelper( ); // The person object for the android message style notification - final avatar = event.room.avatar - ?.getThumbnail( - client, - width: 256, - height: 256, - ) - .toString(); + final avatar = event.room.avatar; final senderAvatar = event.room.isDirectChat ? avatar - : event.senderFromMemoryOrFallback.avatarUrl - ?.getThumbnail( - client, - width: 256, - height: 256, - ) - .toString(); + : event.senderFromMemoryOrFallback.avatarUrl; - File? roomAvatarFile, senderAvatarFile; + Uint8List? roomAvatarFile, senderAvatarFile; try { roomAvatarFile = avatar == null ? null - : await DefaultCacheManager().getSingleFile(avatar); + : await client + .downloadMxcCached( + avatar, + thumbnailMethod: ThumbnailMethod.scale, + width: 256, + height: 256, + animated: false, + isThumbnail: true, + ) + .timeout(const Duration(seconds: 3)); } catch (e, s) { Logs().e('Unable to get avatar picture', e, s); } @@ -207,7 +203,16 @@ Future _tryPushHelper( ? roomAvatarFile : senderAvatar == null ? null - : await DefaultCacheManager().getSingleFile(senderAvatar); + : await client + .downloadMxcCached( + senderAvatar, + thumbnailMethod: ThumbnailMethod.scale, + width: 256, + height: 256, + animated: false, + isThumbnail: true, + ) + .timeout(const Duration(seconds: 3)); } catch (e, s) { Logs().e('Unable to get avatar picture', e, s); } @@ -225,7 +230,7 @@ Future _tryPushHelper( name: event.senderFromMemoryOrFallback.calcDisplayname(), icon: senderAvatarFile == null ? null - : BitmapFilePathAndroidIcon(senderAvatarFile.path), + : ByteArrayAndroidIcon(senderAvatarFile), ), ); @@ -272,7 +277,7 @@ Future _tryPushHelper( name: event.senderFromMemoryOrFallback.calcDisplayname(), icon: roomAvatarFile == null ? null - : BitmapFilePathAndroidIcon(roomAvatarFile.path), + : ByteArrayAndroidIcon(roomAvatarFile), key: event.roomId, important: event.room.isFavourite, ), @@ -321,7 +326,7 @@ Future _setShortcut( Event event, L10n l10n, String title, - File? avatarFile, + Uint8List? avatarFile, ) async { final flutterShortcuts = FlutterShortcuts(); await flutterShortcuts.initialize(debug: !kReleaseMode); @@ -333,8 +338,7 @@ Future _setShortcut( conversationShortcut: true, icon: avatarFile == null ? null - : ShortcutMemoryIcon(jpegImage: await avatarFile.readAsBytes()) - .toString(), + : ShortcutMemoryIcon(jpegImage: avatarFile).toString(), shortcutIconAsset: avatarFile == null ? ShortcutIconAsset.androidAsset : ShortcutIconAsset.memoryAsset, diff --git a/lib/widgets/local_notifications_extension.dart b/lib/widgets/local_notifications_extension.dart index e3d9ff3c4..99fd00cf9 100644 --- a/lib/widgets/local_notifications_extension.dart +++ b/lib/widgets/local_notifications_extension.dart @@ -6,12 +6,11 @@ import 'package:flutter/material.dart'; import 'package:desktop_notifications/desktop_notifications.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:go_router/go_router.dart'; -import 'package:http/http.dart' as http; import 'package:matrix/matrix.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:universal_html/html.dart' as html; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/utils/client_download_content_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -48,50 +47,38 @@ extension LocalNotificationsExtension on MatrixState { hideEdit: true, removeMarkdown: true, ); - final icon = event.senderFromMemoryOrFallback.avatarUrl?.getThumbnail( - client, - width: 64, - height: 64, - method: ThumbnailMethod.crop, - ) ?? - room.avatar?.getThumbnail( - client, - width: 64, - height: 64, - method: ThumbnailMethod.crop, - ); + if (kIsWeb) { + final avatarUrl = event.senderFromMemoryOrFallback.avatarUrl; + + final iconBytes = avatarUrl == null + ? null + : await client.downloadMxcCached( + avatarUrl, + width: 64, + height: 64, + thumbnailMethod: ThumbnailMethod.crop, + isThumbnail: true, + animated: false, + ); + _audioPlayer.play(); + html.Notification( title, body: body, - icon: icon.toString(), + icon: iconBytes == null + ? null + : html.Url.createObjectUrl(html.Blob(iconBytes)), + tag: event.room.id, ); } else if (Platform.isLinux) { - final appIconUrl = room.avatar?.getThumbnail( - room.client, - width: 56, - height: 56, - ); - File? appIconFile; - if (appIconUrl != null) { - final tempDirectory = await getApplicationSupportDirectory(); - final avatarDirectory = - await Directory('${tempDirectory.path}/notiavatars/').create(); - appIconFile = File( - '${avatarDirectory.path}/${Uri.encodeComponent(appIconUrl.toString())}', - ); - if (await appIconFile.exists() == false) { - final response = await http.get(appIconUrl); - await appIconFile.writeAsBytes(response.bodyBytes); - } - } final notification = await linuxNotifications!.notify( title, body: body, replacesId: linuxNotificationIds[roomId] ?? 0, appName: AppConfig.applicationName, - appIcon: appIconFile?.path ?? '', + appIcon: 'fluffychat', actions: [ NotificationAction( DesktopNotificationActions.openChat.name, diff --git a/lib/widgets/mxc_image.dart b/lib/widgets/mxc_image.dart index 2126a8cbe..d776cfc82 100644 --- a/lib/widgets/mxc_image.dart +++ b/lib/widgets/mxc_image.dart @@ -2,10 +2,10 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; -import 'package:http/http.dart' as http; import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/utils/client_download_content_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_file_extension.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -63,8 +63,6 @@ class _MxcImageState extends State { : _imageDataCache[cacheKey] = data; } - bool? _isCached; - Future _load() async { final client = widget.client ?? Matrix.of(context).client; final uri = widget.uri; @@ -77,45 +75,18 @@ class _MxcImageState extends State { final height = widget.height; final realHeight = height == null ? null : height * devicePixelRatio; - final httpUri = widget.isThumbnail - ? uri.getThumbnail( - client, - width: realWidth, - height: realHeight, - animated: widget.animated, - method: widget.thumbnailMethod, - ) - : uri.getDownloadLink(client); - - final storeKey = widget.isThumbnail ? httpUri : uri; - - if (_isCached == null) { - final cachedData = await client.database?.getFile(storeKey); - if (cachedData != null) { - if (!mounted) return; - setState(() { - _imageData = cachedData; - _isCached = true; - }); - return; - } - _isCached = false; - } - - final response = await http.get(httpUri); - if (response.statusCode != 200) { - if (response.statusCode == 404) { - return; - } - throw Exception(); - } - final remoteData = response.bodyBytes; - + final remoteData = await client.downloadMxcCached( + uri, + width: realWidth, + height: realHeight, + thumbnailMethod: widget.thumbnailMethod, + isThumbnail: widget.isThumbnail, + animated: widget.animated, + ); if (!mounted) return; setState(() { _imageData = remoteData; }); - await client.database?.storeFile(storeKey, remoteData, 0); } if (event != null) { @@ -179,7 +150,6 @@ class _MxcImageState extends State { filterQuality: widget.isThumbnail ? FilterQuality.low : FilterQuality.medium, errorBuilder: (context, __, ___) { - _isCached = false; _imageData = null; WidgetsBinding.instance.addPostFrameCallback(_tryLoad); return placeholder(context); diff --git a/pubspec.lock b/pubspec.lock index 98aa4393a..2162bf86f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -450,10 +450,10 @@ packages: dependency: "direct main" description: name: flutter_cache_manager - sha256: "395d6b7831f21f3b989ebedbb785545932adb9afe2622c1ffacf7f4b53a7e544" + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" url: "https://pub.dev" source: hosted - version: "3.3.2" + version: "3.4.1" flutter_driver: dependency: transitive description: flutter @@ -892,10 +892,10 @@ packages: dependency: "direct main" description: name: http - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" http_multi_server: dependency: transitive description: @@ -1201,11 +1201,12 @@ packages: matrix: dependency: "direct main" description: - name: matrix - sha256: "4357245df2a64c435456d1faee55cb33a9fd30aa0df97aacd6abd52b68f70aa1" - url: "https://pub.dev" - source: hosted - version: "0.32.0" + path: "." + ref: HEAD + resolved-ref: "8f350760c4a418a1553030dc4b81408185e0fad5" + url: "https://github.com/famedly/matrix-dart-sdk.git" + source: git + version: "0.32.2" meta: dependency: transitive description: @@ -1338,10 +1339,10 @@ packages: dependency: "direct main" description: name: path_provider - sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" path_provider_android: dependency: transitive description: @@ -2279,10 +2280,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.4" + version: "14.2.5" wakelock_plus: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 853df1744..4ebb8a8dd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,7 +29,7 @@ dependencies: flutter: sdk: flutter flutter_app_badger: ^1.5.0 - flutter_cache_manager: ^3.3.1 + flutter_cache_manager: ^3.4.1 flutter_foreground_task: ^6.1.3 flutter_highlighter: ^0.1.1 flutter_html: ^3.0.0-beta.2 @@ -63,7 +63,8 @@ dependencies: keyboard_shortcuts: ^0.1.4 latlong2: ^0.9.1 linkify: ^5.0.0 - matrix: ^0.32.0 + matrix: # Until 0.32.3 is released + git: https://github.com/famedly/matrix-dart-sdk.git native_imaging: ^0.1.1 opus_caf_converter_dart: ^1.0.1 package_info_plus: ^6.0.0 From 987f84f6b49eca370a2f825106547ace19cb1299 Mon Sep 17 00:00:00 2001 From: Krille-chan Date: Sun, 25 Aug 2024 17:32:11 +0200 Subject: [PATCH 5/8] Revert "fix: little issues in `prepare-web` script" --- scripts/prepare-web.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/prepare-web.sh b/scripts/prepare-web.sh index f55e5ed2d..70b15a246 100755 --- a/scripts/prepare-web.sh +++ b/scripts/prepare-web.sh @@ -1,9 +1,7 @@ #!/bin/sh -ve rm -r assets/js/package -mkdir -p assets/js/package -touch assets/js/package/.gitkeep -OLM_VERSION=$(cat pubspec.yaml | yq -r .dependencies.flutter_olm) +OLM_VERSION=$(cat pubspec.yaml | yq .dependencies.flutter_olm) DOWNLOAD_PATH="https://github.com/famedly/olm/releases/download/v$OLM_VERSION/olm.zip" cd assets/js/ && curl -L $DOWNLOAD_PATH > olm.zip && cd ../../ From e2538816a35382d6b412d1ffee221939d32627dc Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sun, 25 Aug 2024 17:41:05 +0200 Subject: [PATCH 6/8] chore: Follow up homeserver picker page --- .../homeserver_picker/homeserver_picker.dart | 1 + .../homeserver_picker_view.dart | 20 ++++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/pages/homeserver_picker/homeserver_picker.dart b/lib/pages/homeserver_picker/homeserver_picker.dart index b2f782f34..32f1356d7 100644 --- a/lib/pages/homeserver_picker/homeserver_picker.dart +++ b/lib/pages/homeserver_picker/homeserver_picker.dart @@ -75,6 +75,7 @@ class HomeserverPickerController extends State { tryCheckHomeserverActionWithoutCooldown([_]) { _checkHomeserverCooldown?.cancel(); + _lastCheckedUrl = null; checkHomeserverAction(); } diff --git a/lib/pages/homeserver_picker/homeserver_picker_view.dart b/lib/pages/homeserver_picker/homeserver_picker_view.dart index 3fd7bd24c..f99f949b9 100644 --- a/lib/pages/homeserver_picker/homeserver_picker_view.dart +++ b/lib/pages/homeserver_picker/homeserver_picker_view.dart @@ -27,7 +27,7 @@ class HomeserverPickerView extends StatelessWidget { title: Text(L10n.of(context)!.addAccount), ) : null, - body: Column( + body: ListView( children: [ // display a prominent banner to import session for TOR browser // users. This feature is just some UX sugar as TOR users are @@ -55,14 +55,16 @@ class HomeserverPickerView extends StatelessWidget { Image.asset( 'assets/banner_transparent.png', ), - Expanded( - child: ListView( - padding: const EdgeInsets.only( - top: 16.0, - right: 8.0, - left: 8.0, - bottom: 16.0, - ), + Padding( + padding: const EdgeInsets.only( + top: 16.0, + right: 8.0, + left: 8.0, + bottom: 16.0, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.all(16.0), From 0f16cdee88d373d74ed0b8fa88de0e40bbd1113d Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sun, 25 Aug 2024 17:47:23 +0200 Subject: [PATCH 7/8] chore: Better scroll up --- lib/pages/chat/chat.dart | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 7772e5f65..8dc0033aa 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -285,13 +285,23 @@ class ChatController extends State await loadTimelineFuture; if (initialEventId != null) scrollToEventId(initialEventId); - final readMarkerEventIndex = readMarkerEventId.isEmpty + var readMarkerEventIndex = readMarkerEventId.isEmpty ? -1 : timeline!.events - .where((e) => e.isVisibleInGui) + .where((e) => e.isVisibleInGui || e.eventId == readMarkerEventId) .toList() .indexWhere((e) => e.eventId == readMarkerEventId); + // Read marker is existing but not found in first events. Try a single + // requestHistory call before opening timeline on event context: + if (readMarkerEventId.isNotEmpty && readMarkerEventIndex == -1) { + await timeline?.requestHistory(historyCount: _loadHistoryCount); + readMarkerEventIndex = timeline!.events + .where((e) => e.isVisibleInGui || e.eventId == readMarkerEventId) + .toList() + .indexWhere((e) => e.eventId == readMarkerEventId); + } + if (readMarkerEventIndex > 1) { Logs().v('Scroll up to visible event', readMarkerEventId); scrollToEventId(readMarkerEventId, highlightEvent: false); From 1227d762c35492663785b4852fcdefa54b0b7e83 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sun, 25 Aug 2024 18:09:54 +0200 Subject: [PATCH 8/8] chore: Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7132708af..273594b59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ FluffyChat v1.22.0 brings a new design for spaces, replaces the bottom navigation bar with filter chips and makes it finally possible to play ogg audio messages on iOS. A lot of other fixes and improvements have also been added to this release. +FluffyChat also now uses the new authenticated media endpoints if the server supports Matrix v1.11 or +mentions the msc with the key `org.matrix.msc3916.stable` in the `unstable_features`. + - build: (deps): bump docker/build-push-action from 5 to 6 (dependabot[bot]) - build(deps): bump rexml from 3.2.8 to 3.3.3 in /ios (dependabot[bot]) - build: Remove permissions for screensharing until it is fixed (Krille) @@ -24,6 +27,7 @@ FluffyChat v1.22.0 brings a new design for spaces, replaces the bottom navigatio - chore: Sligthly improve chat permissions page design (krille-chan) - design: Add snackbar with link to changelog on new version (Krille) - docs: Update privacy policy (krille-chan) +- feat: Support for matrix auth media endpoints - feat: Convert opus to aac on iOS before playing (Krille) - feat: New spaces and chat list design (krille-chan) - feat: Record voice message with opus/ogg if supported (Krille)