Merge branch 'soru/matrix-uri-scheme' into 'main'

feat: Handle matrix: URIs as per MSC2312

See merge request famedly/fluffychat!330
This commit is contained in:
Sorunome 2021-01-10 17:30:17 +00:00
commit fbe053942f
15 changed files with 41 additions and 101 deletions

View File

@ -38,6 +38,12 @@
android:scheme="https" android:scheme="https"
android:host="matrix.to"/> android:host="matrix.to"/>
</intent-filter> </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> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
@ -67,4 +73,4 @@
android:name="flutterEmbedding" android:name="flutterEmbedding"
android:value="2" /> android:value="2" />
</application> </application>
</manifest> </manifest>

View File

@ -22,6 +22,7 @@ abstract class AppConfig {
static const bool hideTypingUsernames = false; static const bool hideTypingUsernames = false;
static const bool hideAllStateEvents = false; static const bool hideAllStateEvents = false;
static const String inviteLinkPrefix = 'https://matrix.to/#/'; static const String inviteLinkPrefix = 'https://matrix.to/#/';
static const String schemePrefix = 'matrix:';
static const String pushNotificationsChannelId = 'fluffychat_push'; static const String pushNotificationsChannelId = 'fluffychat_push';
static const String pushNotificationsChannelName = 'FluffyChat push channel'; static const String pushNotificationsChannelName = 'FluffyChat push channel';
static const String pushNotificationsChannelDescription = static const String pushNotificationsChannelDescription =

View File

@ -1,6 +1,6 @@
import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:famedlysdk/encryption.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:fluffychat/utils/platform_infos.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View File

@ -53,14 +53,15 @@ class HtmlMessage extends StatelessWidget {
maxLines: maxLines, maxLines: maxLines,
onLinkTap: (url) => UrlLauncher(context, url).launchUrl(), onLinkTap: (url) => UrlLauncher(context, url).launchUrl(),
onPillTap: (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; final ratio = MediaQuery.of(context).devicePixelRatio;
return Uri.parse(mxc)?.getThumbnail( return Uri.parse(mxc)?.getThumbnail(
matrix.client, matrix.client,
width: (width ?? 800) * ratio, width: (width ?? 800) * ratio,
height: (height ?? 800) * ratio, height: (height ?? 800) * ratio,
method: ThumbnailMethod.scale, method: ThumbnailMethod.scale,
animated: true, animated: animated,
); );
}, },
setCodeLanguage: (String key, String value) async { setCodeLanguage: (String key, String value) async {
@ -69,11 +70,16 @@ class HtmlMessage extends StatelessWidget {
getCodeLanguage: (String key) async { getCodeLanguage: (String key) async {
return await matrix.store.getItem('${SettingKeys.codeLanguage}.$key'); return await matrix.store.getItem('${SettingKeys.codeLanguage}.$key');
}, },
getPillInfo: (String identifier) async { getPillInfo: (String url) async {
if (room == null) { if (room == null) {
return 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 // we have a user pill
final user = room.getState('m.room.member', identifier); final user = room.getState('m.room.member', identifier);
if (user != null) { if (user != null) {
@ -89,7 +95,7 @@ class HtmlMessage extends StatelessWidget {
} }
return null; return null;
} }
if (identifier[0] == '#') { if (identifier.sigil == '#') {
// we have an alias pill // we have an alias pill
for (final r in room.client.rooms) { for (final r in room.client.rooms) {
final state = r.getState('m.room.canonical_alias'); final state = r.getState('m.room.canonical_alias');
@ -107,7 +113,7 @@ class HtmlMessage extends StatelessWidget {
} }
return null; return null;
} }
if (identifier[0] == '!') { if (identifier.sigil == '!') {
// we have a room ID pill // we have a room ID pill
final r = room.client.getRoomById(identifier); final r = room.client.getRoomById(identifier);
if (r == null) { if (r == null) {

View File

@ -1,5 +1,4 @@
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:famedlysdk/matrix_api.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.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:fluffychat/views/discover_view.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'matrix_identifier_string_extension.dart';
class UrlLauncher { class UrlLauncher {
final String url; final String url;
@ -16,8 +15,9 @@ class UrlLauncher {
const UrlLauncher(this.context, this.url); const UrlLauncher(this.context, this.url);
void launchUrl() { void launchUrl() {
if (url.startsWith(AppConfig.inviteLinkPrefix) || if (url.toLowerCase().startsWith(AppConfig.inviteLinkPrefix) ||
{'#', '@', '!', '+', '\$'}.contains(url[0])) { {'#', '@', '!', '+', '\$'}.contains(url[0]) ||
url.toLowerCase().startsWith(AppConfig.schemePrefix)) {
return openMatrixToUrl(); return openMatrixToUrl();
} }
launch(url); 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:fluffychat/views/chat_permissions_settings.dart';
import 'package:flushbar/flushbar_helper.dart'; import 'package:flushbar/flushbar_helper.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:famedlysdk/matrix_api.dart';
import 'package:file_picker_cross/file_picker_cross.dart'; import 'package:file_picker_cross/file_picker_cross.dart';
import 'package:fluffychat/components/adaptive_page_layout.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()) { if (Navigator.of(context).canPop()) {
Navigator.of(context).popUntil((r) => r.isFirst); 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(); UrlLauncher(context, text).openMatrixToUrl();
return; return;
} }

View File

@ -3,7 +3,6 @@ import 'dart:async';
import 'package:fluffychat/components/default_app_bar_search_field.dart'; import 'package:fluffychat/components/default_app_bar_search_field.dart';
import 'package:flushbar/flushbar_helper.dart'; import 'package:flushbar/flushbar_helper.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:famedlysdk/matrix_api.dart';
import 'package:fluffychat/components/adaptive_page_layout.dart'; import 'package:fluffychat/components/adaptive_page_layout.dart';
import 'package:fluffychat/components/avatar.dart'; import 'package:fluffychat/components/avatar.dart';
import 'package:future_loading_dialog/future_loading_dialog.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:fluffychat/components/adaptive_page_layout.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:fluffychat/components/matrix.dart'; import 'package:fluffychat/components/matrix.dart';
@ -37,9 +37,9 @@ class _NewGroupState extends State<_NewGroup> {
context: context, context: context,
future: () => matrix.client.createRoom( future: () => matrix.client.createRoom(
preset: publicGroup preset: publicGroup
? api.CreateRoomPreset.public_chat ? sdk.CreateRoomPreset.public_chat
: api.CreateRoomPreset.private_chat, : sdk.CreateRoomPreset.private_chat,
visibility: publicGroup ? api.Visibility.public : null, visibility: publicGroup ? sdk.Visibility.public : null,
roomAliasName: roomAliasName:
publicGroup && controller.text.isNotEmpty ? controller.text : null, publicGroup && controller.text.isNotEmpty ? controller.text : null,
name: controller.text.isNotEmpty ? controller.text : null, name: controller.text.isNotEmpty ? controller.text : null,

View File

@ -1,7 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:famedlysdk/matrix_api.dart';
import 'package:fluffychat/components/adaptive_page_layout.dart'; import 'package:fluffychat/components/adaptive_page_layout.dart';
import 'package:fluffychat/components/avatar.dart'; import 'package:fluffychat/components/avatar.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart';

View File

@ -202,7 +202,7 @@ packages:
description: description:
path: "." path: "."
ref: main ref: main
resolved-ref: ed8af418f0ed6087ec5378433281c5ba39585c6f resolved-ref: "0a89ac5564e1a8a98236960aac8eaa251118a051"
url: "https://gitlab.com/famedly/famedlysdk.git" url: "https://gitlab.com/famedly/famedlysdk.git"
source: git source: git
version: "0.0.1" version: "0.0.1"
@ -369,7 +369,7 @@ packages:
name: flutter_matrix_html name: flutter_matrix_html
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.14" version: "0.2.0"
flutter_olm: flutter_olm:
dependency: "direct main" dependency: "direct main"
description: description:
@ -569,6 +569,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.10-nullsafety.1" 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: matrix_file_e2ee:
dependency: transitive dependency: transitive
description: description:

View File

@ -40,7 +40,7 @@ dependencies:
mime_type: ^0.3.2 mime_type: ^0.3.2
flushbar: ^1.10.4 flushbar: ^1.10.4
adaptive_dialog: ^0.9.3 adaptive_dialog: ^0.9.3
flutter_matrix_html: ^0.1.14 flutter_matrix_html: ^0.2.0
moor: ^3.4.0 moor: ^3.4.0
sqlite3_flutter_libs: ^0.3.0 sqlite3_flutter_libs: ^0.3.0
sqlite3: ^0.1.8 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');
});
});
}