mirror of
				https://gitlab.com/famedly/fluffychat.git
				synced 2025-11-04 06:17:26 +01:00 
			
		
		
		
	feat: Handle matrix: URIs as per MSC2312
This commit is contained in:
		
							parent
							
								
									ef9369c9d2
								
							
						
					
					
						commit
						1da643fa31
					
				@ -38,6 +38,12 @@
 | 
			
		||||
                   android:scheme="https"
 | 
			
		||||
                   android:host="matrix.to"/>
 | 
			
		||||
            </intent-filter>
 | 
			
		||||
            <intent-filter>
 | 
			
		||||
               <action android:name="android.intent.action.VIEW" />
 | 
			
		||||
               <category android:name="android.intent.category.DEFAULT" />
 | 
			
		||||
               <category android:name="android.intent.category.BROWSABLE" />
 | 
			
		||||
               <data android:scheme="matrix" />
 | 
			
		||||
            </intent-filter>
 | 
			
		||||
            <intent-filter>
 | 
			
		||||
               <action android:name="android.intent.action.SEND" />
 | 
			
		||||
               <category android:name="android.intent.category.DEFAULT" />
 | 
			
		||||
@ -67,4 +73,4 @@
 | 
			
		||||
            android:name="flutterEmbedding"
 | 
			
		||||
            android:value="2" />
 | 
			
		||||
    </application>
 | 
			
		||||
</manifest>
 | 
			
		||||
</manifest>
 | 
			
		||||
 | 
			
		||||
@ -22,6 +22,7 @@ abstract class AppConfig {
 | 
			
		||||
  static const bool hideTypingUsernames = false;
 | 
			
		||||
  static const bool hideAllStateEvents = false;
 | 
			
		||||
  static const String inviteLinkPrefix = 'https://matrix.to/#/';
 | 
			
		||||
  static const String schemePrefix = 'matrix:';
 | 
			
		||||
  static const String pushNotificationsChannelId = 'fluffychat_push';
 | 
			
		||||
  static const String pushNotificationsChannelName = 'FluffyChat push channel';
 | 
			
		||||
  static const String pushNotificationsChannelDescription =
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
			
		||||
import 'package:famedlysdk/encryption.dart';
 | 
			
		||||
import 'package:famedlysdk/matrix_api.dart';
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:fluffychat/utils/platform_infos.dart';
 | 
			
		||||
import 'package:flutter/cupertino.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
@ -53,14 +53,15 @@ class HtmlMessage extends StatelessWidget {
 | 
			
		||||
      maxLines: maxLines,
 | 
			
		||||
      onLinkTap: (url) => UrlLauncher(context, url).launchUrl(),
 | 
			
		||||
      onPillTap: (url) => UrlLauncher(context, url).launchUrl(),
 | 
			
		||||
      getMxcUrl: (String mxc, double width, double height) {
 | 
			
		||||
      getMxcUrl: (String mxc, double width, double height,
 | 
			
		||||
          {bool animated = false}) {
 | 
			
		||||
        final ratio = MediaQuery.of(context).devicePixelRatio;
 | 
			
		||||
        return Uri.parse(mxc)?.getThumbnail(
 | 
			
		||||
          matrix.client,
 | 
			
		||||
          width: (width ?? 800) * ratio,
 | 
			
		||||
          height: (height ?? 800) * ratio,
 | 
			
		||||
          method: ThumbnailMethod.scale,
 | 
			
		||||
          animated: true,
 | 
			
		||||
          animated: animated,
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
      setCodeLanguage: (String key, String value) async {
 | 
			
		||||
@ -69,11 +70,16 @@ class HtmlMessage extends StatelessWidget {
 | 
			
		||||
      getCodeLanguage: (String key) async {
 | 
			
		||||
        return await matrix.store.getItem('${SettingKeys.codeLanguage}.$key');
 | 
			
		||||
      },
 | 
			
		||||
      getPillInfo: (String identifier) async {
 | 
			
		||||
      getPillInfo: (String url) async {
 | 
			
		||||
        if (room == null) {
 | 
			
		||||
          return null;
 | 
			
		||||
        }
 | 
			
		||||
        if (identifier[0] == '@') {
 | 
			
		||||
        final identityParts = url.parseIdentifierIntoParts();
 | 
			
		||||
        final identifier = identityParts?.primaryIdentifier;
 | 
			
		||||
        if (identifier == null) {
 | 
			
		||||
          return null;
 | 
			
		||||
        }
 | 
			
		||||
        if (identifier.sigil == '@') {
 | 
			
		||||
          // we have a user pill
 | 
			
		||||
          final user = room.getState('m.room.member', identifier);
 | 
			
		||||
          if (user != null) {
 | 
			
		||||
@ -89,7 +95,7 @@ class HtmlMessage extends StatelessWidget {
 | 
			
		||||
          }
 | 
			
		||||
          return null;
 | 
			
		||||
        }
 | 
			
		||||
        if (identifier[0] == '#') {
 | 
			
		||||
        if (identifier.sigil == '#') {
 | 
			
		||||
          // we have an alias pill
 | 
			
		||||
          for (final r in room.client.rooms) {
 | 
			
		||||
            final state = r.getState('m.room.canonical_alias');
 | 
			
		||||
@ -107,7 +113,7 @@ class HtmlMessage extends StatelessWidget {
 | 
			
		||||
          }
 | 
			
		||||
          return null;
 | 
			
		||||
        }
 | 
			
		||||
        if (identifier[0] == '!') {
 | 
			
		||||
        if (identifier.sigil == '!') {
 | 
			
		||||
          // we have a room ID pill
 | 
			
		||||
          final r = room.client.getRoomById(identifier);
 | 
			
		||||
          if (r == null) {
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,4 @@
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:famedlysdk/matrix_api.dart';
 | 
			
		||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
 | 
			
		||||
@ -1,33 +0,0 @@
 | 
			
		||||
import '../app_config.dart';
 | 
			
		||||
 | 
			
		||||
extension MatrixIdentifierStringExtension on String {
 | 
			
		||||
  /// Separates room identifiers with an event id and possibly a query parameter into its components.
 | 
			
		||||
  MatrixIdentifierStringExtensionResults parseIdentifierIntoParts() {
 | 
			
		||||
    final isUrl = startsWith(AppConfig.inviteLinkPrefix);
 | 
			
		||||
    var s = this;
 | 
			
		||||
    if (isUrl) {
 | 
			
		||||
      // as we decode a component we may only call it on the url part *before* the "query" part
 | 
			
		||||
      final parts = replaceFirst(AppConfig.inviteLinkPrefix, '').split('?');
 | 
			
		||||
      s = Uri.decodeComponent(parts.removeAt(0)) + '?' + parts.join('?');
 | 
			
		||||
    }
 | 
			
		||||
    final match = RegExp(r'^([#!@+][^:]*:[^\/?]*)(?:\/(\$[^?]*))?(?:\?(.*))?$')
 | 
			
		||||
        .firstMatch(s);
 | 
			
		||||
    if (match == null) {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
    return MatrixIdentifierStringExtensionResults(
 | 
			
		||||
      primaryIdentifier: match.group(1),
 | 
			
		||||
      secondaryIdentifier: match.group(2),
 | 
			
		||||
      queryString: match.group(3)?.isNotEmpty ?? false ? match.group(3) : null,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class MatrixIdentifierStringExtensionResults {
 | 
			
		||||
  final String primaryIdentifier;
 | 
			
		||||
  final String secondaryIdentifier;
 | 
			
		||||
  final String queryString;
 | 
			
		||||
 | 
			
		||||
  MatrixIdentifierStringExtensionResults(
 | 
			
		||||
      {this.primaryIdentifier, this.secondaryIdentifier, this.queryString});
 | 
			
		||||
}
 | 
			
		||||
@ -8,7 +8,6 @@ import 'package:fluffychat/views/chat.dart';
 | 
			
		||||
import 'package:fluffychat/views/discover_view.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:url_launcher/url_launcher.dart';
 | 
			
		||||
import 'matrix_identifier_string_extension.dart';
 | 
			
		||||
 | 
			
		||||
class UrlLauncher {
 | 
			
		||||
  final String url;
 | 
			
		||||
@ -16,8 +15,9 @@ class UrlLauncher {
 | 
			
		||||
  const UrlLauncher(this.context, this.url);
 | 
			
		||||
 | 
			
		||||
  void launchUrl() {
 | 
			
		||||
    if (url.startsWith(AppConfig.inviteLinkPrefix) ||
 | 
			
		||||
        {'#', '@', '!', '+', '\$'}.contains(url[0])) {
 | 
			
		||||
    if (url.toLowerCase().startsWith(AppConfig.inviteLinkPrefix) ||
 | 
			
		||||
        {'#', '@', '!', '+', '\$'}.contains(url[0]) ||
 | 
			
		||||
        url.toLowerCase().startsWith(AppConfig.schemePrefix)) {
 | 
			
		||||
      return openMatrixToUrl();
 | 
			
		||||
    }
 | 
			
		||||
    launch(url);
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,6 @@ import 'package:fluffychat/utils/fluffy_share.dart';
 | 
			
		||||
import 'package:fluffychat/views/chat_permissions_settings.dart';
 | 
			
		||||
import 'package:flushbar/flushbar_helper.dart';
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:famedlysdk/matrix_api.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:file_picker_cross/file_picker_cross.dart';
 | 
			
		||||
import 'package:fluffychat/components/adaptive_page_layout.dart';
 | 
			
		||||
 | 
			
		||||
@ -105,7 +105,9 @@ class _ChatListState extends State<ChatList> {
 | 
			
		||||
    if (Navigator.of(context).canPop()) {
 | 
			
		||||
      Navigator.of(context).popUntil((r) => r.isFirst);
 | 
			
		||||
    }
 | 
			
		||||
    if (text.startsWith(AppConfig.inviteLinkPrefix)) {
 | 
			
		||||
    if (text.toLowerCase().startsWith(AppConfig.inviteLinkPrefix) ||
 | 
			
		||||
        (text.toLowerCase().startsWith(AppConfig.schemePrefix) &&
 | 
			
		||||
            !RegExp(r'\s').hasMatch(text))) {
 | 
			
		||||
      UrlLauncher(context, text).openMatrixToUrl();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,6 @@ import 'dart:async';
 | 
			
		||||
import 'package:fluffychat/components/default_app_bar_search_field.dart';
 | 
			
		||||
import 'package:flushbar/flushbar_helper.dart';
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:famedlysdk/matrix_api.dart';
 | 
			
		||||
import 'package:fluffychat/components/adaptive_page_layout.dart';
 | 
			
		||||
import 'package:fluffychat/components/avatar.dart';
 | 
			
		||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import 'package:famedlysdk/matrix_api.dart' as api;
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart' as sdk;
 | 
			
		||||
import 'package:fluffychat/components/adaptive_page_layout.dart';
 | 
			
		||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
			
		||||
import 'package:fluffychat/components/matrix.dart';
 | 
			
		||||
@ -37,9 +37,9 @@ class _NewGroupState extends State<_NewGroup> {
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () => matrix.client.createRoom(
 | 
			
		||||
        preset: publicGroup
 | 
			
		||||
            ? api.CreateRoomPreset.public_chat
 | 
			
		||||
            : api.CreateRoomPreset.private_chat,
 | 
			
		||||
        visibility: publicGroup ? api.Visibility.public : null,
 | 
			
		||||
            ? sdk.CreateRoomPreset.public_chat
 | 
			
		||||
            : sdk.CreateRoomPreset.private_chat,
 | 
			
		||||
        visibility: publicGroup ? sdk.Visibility.public : null,
 | 
			
		||||
        roomAliasName:
 | 
			
		||||
            publicGroup && controller.text.isNotEmpty ? controller.text : null,
 | 
			
		||||
        name: controller.text.isNotEmpty ? controller.text : null,
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,6 @@
 | 
			
		||||
import 'dart:async';
 | 
			
		||||
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:famedlysdk/matrix_api.dart';
 | 
			
		||||
import 'package:fluffychat/components/adaptive_page_layout.dart';
 | 
			
		||||
import 'package:fluffychat/components/avatar.dart';
 | 
			
		||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										11
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								pubspec.lock
									
									
									
									
									
								
							@ -202,7 +202,7 @@ packages:
 | 
			
		||||
    description:
 | 
			
		||||
      path: "."
 | 
			
		||||
      ref: main
 | 
			
		||||
      resolved-ref: ed8af418f0ed6087ec5378433281c5ba39585c6f
 | 
			
		||||
      resolved-ref: "0a89ac5564e1a8a98236960aac8eaa251118a051"
 | 
			
		||||
      url: "https://gitlab.com/famedly/famedlysdk.git"
 | 
			
		||||
    source: git
 | 
			
		||||
    version: "0.0.1"
 | 
			
		||||
@ -369,7 +369,7 @@ packages:
 | 
			
		||||
      name: flutter_matrix_html
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.1.14"
 | 
			
		||||
    version: "0.2.0"
 | 
			
		||||
  flutter_olm:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
@ -569,6 +569,13 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.12.10-nullsafety.1"
 | 
			
		||||
  matrix_api_lite:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: matrix_api_lite
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.1.4"
 | 
			
		||||
  matrix_file_e2ee:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
 | 
			
		||||
@ -40,7 +40,7 @@ dependencies:
 | 
			
		||||
  mime_type: ^0.3.2
 | 
			
		||||
  flushbar: ^1.10.4
 | 
			
		||||
  adaptive_dialog: ^0.9.3
 | 
			
		||||
  flutter_matrix_html: ^0.1.14
 | 
			
		||||
  flutter_matrix_html: ^0.2.0
 | 
			
		||||
  moor: ^3.4.0
 | 
			
		||||
  sqlite3_flutter_libs: ^0.3.0
 | 
			
		||||
  sqlite3: ^0.1.8
 | 
			
		||||
 | 
			
		||||
@ -1,45 +0,0 @@
 | 
			
		||||
import 'package:flutter_test/flutter_test.dart';
 | 
			
		||||
import 'package:fluffychat/utils/matrix_identifier_string_extension.dart';
 | 
			
		||||
 | 
			
		||||
void main() {
 | 
			
		||||
  group('Matrix Identifier String Extension', () {
 | 
			
		||||
    test('parseIdentifierIntoParts', () {
 | 
			
		||||
      var res = '#alias:beep'.parseIdentifierIntoParts();
 | 
			
		||||
      expect(res.primaryIdentifier, '#alias:beep');
 | 
			
		||||
      expect(res.secondaryIdentifier, null);
 | 
			
		||||
      expect(res.queryString, null);
 | 
			
		||||
      res = 'blha'.parseIdentifierIntoParts();
 | 
			
		||||
      expect(res, null);
 | 
			
		||||
      res = '#alias:beep/\$event'.parseIdentifierIntoParts();
 | 
			
		||||
      expect(res.primaryIdentifier, '#alias:beep');
 | 
			
		||||
      expect(res.secondaryIdentifier, '\$event');
 | 
			
		||||
      expect(res.queryString, null);
 | 
			
		||||
      res = '#alias:beep?blubb'.parseIdentifierIntoParts();
 | 
			
		||||
      expect(res.primaryIdentifier, '#alias:beep');
 | 
			
		||||
      expect(res.secondaryIdentifier, null);
 | 
			
		||||
      expect(res.queryString, 'blubb');
 | 
			
		||||
      res = '#alias:beep/\$event?blubb'.parseIdentifierIntoParts();
 | 
			
		||||
      expect(res.primaryIdentifier, '#alias:beep');
 | 
			
		||||
      expect(res.secondaryIdentifier, '\$event');
 | 
			
		||||
      expect(res.queryString, 'blubb');
 | 
			
		||||
      res = '#/\$?:beep/\$event?blubb?b'.parseIdentifierIntoParts();
 | 
			
		||||
      expect(res.primaryIdentifier, '#/\$?:beep');
 | 
			
		||||
      expect(res.secondaryIdentifier, '\$event');
 | 
			
		||||
      expect(res.queryString, 'blubb?b');
 | 
			
		||||
 | 
			
		||||
      res = 'https://matrix.to/#/#alias:beep'.parseIdentifierIntoParts();
 | 
			
		||||
      expect(res.primaryIdentifier, '#alias:beep');
 | 
			
		||||
      expect(res.secondaryIdentifier, null);
 | 
			
		||||
      expect(res.queryString, null);
 | 
			
		||||
      res = 'https://matrix.to/#/%23alias%3abeep'.parseIdentifierIntoParts();
 | 
			
		||||
      expect(res.primaryIdentifier, '#alias:beep');
 | 
			
		||||
      expect(res.secondaryIdentifier, null);
 | 
			
		||||
      expect(res.queryString, null);
 | 
			
		||||
      res = 'https://matrix.to/#/%23alias%3abeep?boop%F0%9F%A7%A1%F0%9F%A6%8A'
 | 
			
		||||
          .parseIdentifierIntoParts();
 | 
			
		||||
      expect(res.primaryIdentifier, '#alias:beep');
 | 
			
		||||
      expect(res.secondaryIdentifier, null);
 | 
			
		||||
      expect(res.queryString, 'boop%F0%9F%A7%A1%F0%9F%A6%8A');
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user