feat: Handle matrix: URIs as per MSC2312

This commit is contained in:
Sorunome 2021-01-10 18:00:27 +01:00
parent ef9369c9d2
commit 1da643fa31
No known key found for this signature in database
GPG Key ID: B19471D07FC9BE9C
15 changed files with 41 additions and 101 deletions

View File

@ -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" />

View File

@ -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 =

View File

@ -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';

View File

@ -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) {

View File

@ -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';

View File

@ -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});
}

View File

@ -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);

View File

@ -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';

View File

@ -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;
}

View File

@ -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';

View File

@ -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,

View File

@ -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';

View File

@ -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:

View File

@ -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

View File

@ -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');
});
});
}