refactor: Update SDK

This commit is contained in:
Christian Pauly 2021-05-20 13:59:55 +02:00
parent e1bd4e174c
commit 4f13473593
26 changed files with 57 additions and 187 deletions

View File

@ -133,7 +133,7 @@ class BackgroundPush {
} }
final clientName = PlatformInfos.clientName; final clientName = PlatformInfos.clientName;
oldTokens ??= <String>{}; oldTokens ??= <String>{};
final pushers = await client.requestPushers().catchError((e) { final pushers = await client.getPushers().catchError((e) {
Logs().w('[Push] Unable to request pushers', e); Logs().w('[Push] Unable to request pushers', e);
return <Pusher>[]; return <Pusher>[];
}); });
@ -179,7 +179,7 @@ class BackgroundPush {
oldTokens.contains(pusher.pushkey)) { oldTokens.contains(pusher.pushkey)) {
pusher.kind = null; pusher.kind = null;
try { try {
await client.setPusher( await client.postPusher(
pusher, pusher,
append: true, append: true,
); );
@ -191,7 +191,7 @@ class BackgroundPush {
} }
if (setNewPusher) { if (setNewPusher) {
try { try {
await client.setPusher( await client.postPusher(
Pusher( Pusher(
token, token,
thisAppId, thisAppId,
@ -534,7 +534,7 @@ class BackgroundPush {
try { try {
Logs().v( Logs().v(
'[Push] failed to sync for fallback push, fetching notifications endpoint...'); '[Push] failed to sync for fallback push, fetching notifications endpoint...');
final notifications = await client.requestNotifications(limit: 20); final notifications = await client.getNotifications(limit: 20);
final notificationRooms = final notificationRooms =
notifications.notifications.map((n) => n.roomId).toSet(); notifications.notifications.map((n) => n.roomId).toSet();
emptyRooms = client.rooms emptyRooms = client.rooms

View File

@ -45,8 +45,7 @@ class UrlLauncher {
// we were unable to find the room locally...so resolve it // we were unable to find the room locally...so resolve it
final response = await showFutureLoadingDialog( final response = await showFutureLoadingDialog(
context: context, context: context,
future: () => future: () => matrix.client.getRoomIdByAlias(roomIdOrAlias),
matrix.client.requestRoomAliasInformation(roomIdOrAlias),
); );
if (response.error != null) { if (response.error != null) {
return; // nothing to do, the alias doesn't exist return; // nothing to do, the alias doesn't exist
@ -79,7 +78,7 @@ class UrlLauncher {
roomId = roomIdOrAlias; roomId = roomIdOrAlias;
final response = await showFutureLoadingDialog( final response = await showFutureLoadingDialog(
context: context, context: context,
future: () => matrix.client.joinRoomOrAlias( future: () => matrix.client.joinRoom(
roomIdOrAlias, roomIdOrAlias,
servers: servers.isNotEmpty ? servers.toList() : null, servers: servers.isNotEmpty ? servers.toList() : null,
), ),

View File

@ -181,7 +181,7 @@ class ChatController extends State<Chat> {
timeline.events.isNotEmpty && timeline.events.isNotEmpty &&
Matrix.of(context).webHasFocus) { Matrix.of(context).webHasFocus) {
// ignore: unawaited_futures // ignore: unawaited_futures
room.sendReadMarker( room.setReadMarker(
timeline.events.first.eventId, timeline.events.first.eventId,
readReceiptLocationEventId: timeline.events.first.eventId, readReceiptLocationEventId: timeline.events.first.eventId,
); );
@ -351,7 +351,7 @@ class ChatController extends State<Chat> {
if (reason == null || reason.single.isEmpty) return; if (reason == null || reason.single.isEmpty) return;
final result = await showFutureLoadingDialog( final result = await showFutureLoadingDialog(
context: context, context: context,
future: () => Matrix.of(context).client.reportEvent( future: () => Matrix.of(context).client.reportContent(
event.roomId, event.roomId,
event.eventId, event.eventId,
reason.single, reason.single,
@ -377,7 +377,8 @@ class ChatController extends State<Chat> {
for (final event in selectedEvents) { for (final event in selectedEvents) {
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => event.status > 0 ? event.redact() : event.remove()); future: () =>
event.status > 0 ? event.redactEvent() : event.remove());
} }
setState(() => selectedEvents.clear()); setState(() => selectedEvents.clear());
} }
@ -629,7 +630,7 @@ class ChatController extends State<Chat> {
typingCoolDown = Timer(Duration(seconds: 2), () { typingCoolDown = Timer(Duration(seconds: 2), () {
typingCoolDown = null; typingCoolDown = null;
currentlyTyping = false; currentlyTyping = false;
room.sendTypingNotification(false); room.setTyping(false);
}); });
typingTimeout ??= Timer(Duration(seconds: 30), () { typingTimeout ??= Timer(Duration(seconds: 30), () {
typingTimeout = null; typingTimeout = null;
@ -637,8 +638,7 @@ class ChatController extends State<Chat> {
}); });
if (!currentlyTyping) { if (!currentlyTyping) {
currentlyTyping = true; currentlyTyping = true;
room.sendTypingNotification(true, room.setTyping(true, timeout: Duration(seconds: 30).inMilliseconds);
timeout: Duration(seconds: 30).inMilliseconds);
} }
setState(() => inputText = text); setState(() => inputText = text);
} }

View File

@ -143,14 +143,14 @@ class ChatDetailsController extends State<ChatDetails> {
case AliasActions.delete: case AliasActions.delete:
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => room.client.removeRoomAlias(select), future: () => room.client.deleteRoomAlias(select),
); );
break; break;
case AliasActions.setCanonical: case AliasActions.setCanonical:
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => future: () => room.client
room.client.sendState(room.id, EventTypes.RoomCanonicalAlias, { .setRoomStateWithKey(room.id, EventTypes.RoomCanonicalAlias, {
'alias': select, 'alias': select,
}), }),
); );
@ -180,8 +180,8 @@ class ChatDetailsController extends State<ChatDetails> {
if (input == null) return; if (input == null) return;
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => room.client future: () =>
.createRoomAlias('#' + input.single + ':' + domain, room.id), room.client.setRoomAlias('#' + input.single + ':' + domain, room.id),
); );
} }

View File

@ -184,7 +184,7 @@ class ChatListController extends State<ChatList> {
if (input == null) return; if (input == null) return;
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => Matrix.of(context).client.sendPresence( future: () => Matrix.of(context).client.setPresence(
Matrix.of(context).client.userID, Matrix.of(context).client.userID,
PresenceType.online, PresenceType.online,
statusMsg: input.single, statusMsg: input.single,

View File

@ -47,8 +47,8 @@ class ChatPermissionsSettingsController extends State<ChatPermissionsSettings> {
inspect(content); inspect(content);
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => future: () => room.client
room.client.sendState(room.id, EventTypes.RoomPowerLevels, content), .setRoomStateWithKey(room.id, EventTypes.RoomPowerLevels, content),
); );
} }

View File

@ -18,7 +18,7 @@ class DevicesSettingsController extends State<DevicesSettings> {
List<Device> devices; List<Device> devices;
Future<bool> loadUserDevices(BuildContext context) async { Future<bool> loadUserDevices(BuildContext context) async {
if (devices != null) return true; if (devices != null) return true;
devices = await Matrix.of(context).client.requestDevices(); devices = await Matrix.of(context).client.getDevices();
return true; return true;
} }
@ -80,7 +80,7 @@ class DevicesSettingsController extends State<DevicesSettings> {
context: context, context: context,
future: () => Matrix.of(context) future: () => Matrix.of(context)
.client .client
.setDeviceMetadata(device.deviceId, displayName: displayName.single), .updateDevice(device.deviceId, displayName: displayName.single),
); );
if (success.error == null) { if (success.error == null) {
reload(); reload();

View File

@ -110,7 +110,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
AppConfig.jitsiInstance = jitsi; AppConfig.jitsiInstance = jitsi;
} }
final loginTypes = await Matrix.of(context).client.requestLoginTypes(); final loginTypes = await Matrix.of(context).client.getLoginFlows();
if (loginTypes.flows if (loginTypes.flows
.any((flow) => flow.type == AuthenticationTypes.password)) { .any((flow) => flow.type == AuthenticationTypes.password)) {
await AdaptivePageLayout.of(context) await AdaptivePageLayout.of(context)

View File

@ -87,7 +87,7 @@ class InvitationSelectionController extends State<InvitationSelection> {
final matrix = Matrix.of(context); final matrix = Matrix.of(context);
UserSearchResult response; UserSearchResult response;
try { try {
response = await matrix.client.searchUser(text, limit: 10); response = await matrix.client.searchUserDirectory(text, limit: 10);
} catch (e) { } catch (e) {
AdaptivePageLayout.of(context).showSnackBar( AdaptivePageLayout.of(context).showSnackBar(
SnackBar(content: Text((e as Object).toLocalizedString(context)))); SnackBar(content: Text((e as Object).toLocalizedString(context))));

View File

@ -73,7 +73,7 @@ class NewPrivateChatController extends State<NewPrivateChat> {
final matrix = Matrix.of(context); final matrix = Matrix.of(context);
UserSearchResult response; UserSearchResult response;
try { try {
response = await matrix.client.searchUser(text, limit: 10); response = await matrix.client.searchUserDirectory(text, limit: 10);
} catch (_) {} } catch (_) {}
setState(() => loading = false); setState(() => loading = false);
if (response?.results?.isEmpty ?? true) return; if (response?.results?.isEmpty ?? true) return;

View File

@ -48,7 +48,7 @@ class SearchController extends State<Search> {
} }
final newRoomId = await Matrix.of(context) final newRoomId = await Matrix.of(context)
.client .client
.joinRoomOrAlias(alias?.isNotEmpty ?? false ? alias : roomId); .joinRoom(alias?.isNotEmpty ?? false ? alias : roomId);
await Matrix.of(context) await Matrix.of(context)
.client .client
.onRoomUpdate .onRoomUpdate
@ -120,7 +120,7 @@ class SearchController extends State<Search> {
final matrix = Matrix.of(context); final matrix = Matrix.of(context);
UserSearchResult response; UserSearchResult response;
try { try {
response = await matrix.client.searchUser(text, limit: 10); response = await matrix.client.searchUserDirectory(text, limit: 10);
} catch (_) {} } catch (_) {}
foundProfiles = List<Profile>.from(response?.results ?? []); foundProfiles = List<Profile>.from(response?.results ?? []);
if (foundProfiles.isEmpty && text.isValidMatrixId && text.sigil == '@') { if (foundProfiles.isEmpty && text.isValidMatrixId && text.sigil == '@') {

View File

@ -182,7 +182,7 @@ class SettingsController extends State<Settings> {
final success = await showFutureLoadingDialog( final success = await showFutureLoadingDialog(
context: context, context: context,
future: () => future: () =>
matrix.client.setDisplayname(matrix.client.userID, input.single), matrix.client.setDisplayName(matrix.client.userID, input.single),
); );
if (success.error == null) { if (success.error == null) {
setState(() { setState(() {

View File

@ -51,7 +51,7 @@ class Settings3PidController extends State<Settings3Pid> {
final success = await showFutureLoadingDialog( final success = await showFutureLoadingDialog(
context: context, context: context,
future: () => Matrix.of(context).client.uiaRequestBackground( future: () => Matrix.of(context).client.uiaRequestBackground(
(auth) => Matrix.of(context).client.addThirdPartyIdentifier( (auth) => Matrix.of(context).client.add3PID(
clientSecret, clientSecret,
response.result.sid, response.result.sid,
auth: auth, auth: auth,
@ -77,7 +77,7 @@ class Settings3PidController extends State<Settings3Pid> {
} }
final success = await showFutureLoadingDialog( final success = await showFutureLoadingDialog(
context: context, context: context,
future: () => Matrix.of(context).client.deleteThirdPartyIdentifier( future: () => Matrix.of(context).client.delete3pidFromAccount(
identifier.address, identifier.address,
identifier.medium, identifier.medium,
)); ));

View File

@ -77,8 +77,8 @@ class EmotesSettingsController extends State<EmotesSettings> {
if (widget.room != null) { if (widget.room != null) {
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => client.sendState(widget.room.id, 'im.ponies.room_emotes', future: () => client.setRoomStateWithKey(widget.room.id,
content, widget.stateKey ?? ''), 'im.ponies.room_emotes', content, widget.stateKey ?? ''),
); );
} else { } else {
await showFutureLoadingDialog( await showFutureLoadingDialog(
@ -249,7 +249,8 @@ class EmotesSettingsController extends State<EmotesSettings> {
} }
final uploadResp = await showFutureLoadingDialog( final uploadResp = await showFutureLoadingDialog(
context: context, context: context,
future: () => Matrix.of(context).client.upload(file.bytes, file.name), future: () =>
Matrix.of(context).client.uploadContent(file.bytes, file.name),
); );
if (uploadResp.error == null) { if (uploadResp.error == null) {
setState(() { setState(() {

View File

@ -98,7 +98,7 @@ class SettingsNotificationsController extends State<SettingsNotifications> {
void setNotificationSetting(NotificationSettingsItem item, bool enabled) { void setNotificationSetting(NotificationSettingsItem item, bool enabled) {
showFutureLoadingDialog( showFutureLoadingDialog(
context: context, context: context,
future: () => Matrix.of(context).client.enablePushRule( future: () => Matrix.of(context).client.setPushRuleEnabled(
'global', 'global',
item.type, item.type,
item.key, item.key,

View File

@ -51,7 +51,7 @@ class SignUpController extends State<SignUp> {
usernameController.text.toLowerCase().trim().replaceAll(' ', '-'); usernameController.text.toLowerCase().trim().replaceAll(' ', '-');
try { try {
await matrix.client.usernameAvailable(preferredUsername); await matrix.client.checkUsernameAvailability(preferredUsername);
} on MatrixException catch (exception) { } on MatrixException catch (exception) {
setState(() => usernameError = exception.errorMessage); setState(() => usernameError = exception.errorMessage);
return setState(() => loading = false); return setState(() => loading = false);

View File

@ -79,7 +79,7 @@ class SignUpPasswordController extends State<SignUpPassword> {
if (matrix.currentClientSecret != null && if (matrix.currentClientSecret != null &&
matrix.currentThreepidCreds != null) { matrix.currentThreepidCreds != null) {
Logs().d('Add third party identifier'); Logs().d('Add third party identifier');
await matrix.client.addThirdPartyIdentifier( await matrix.client.add3PID(
matrix.currentClientSecret, matrix.currentClientSecret,
matrix.currentThreepidCreds.sid, matrix.currentThreepidCreds.sid,
); );
@ -94,7 +94,7 @@ class SignUpPasswordController extends State<SignUpPassword> {
// tchncs.de // tchncs.de
try { try {
await matrix.client await matrix.client
.setDisplayname(matrix.client.userID, widget.displayname); .setDisplayName(matrix.client.userID, widget.displayname);
} catch (exception) { } catch (exception) {
AdaptivePageLayout.of(context).showSnackBar( AdaptivePageLayout.of(context).showSnackBar(
SnackBar(content: Text(L10n.of(context).couldNotSetDisplayname))); SnackBar(content: Text(L10n.of(context).couldNotSetDisplayname)));

View File

@ -93,7 +93,7 @@ class ChatPermissionsSettingsUI extends StatelessWidget {
if (room.canSendEvent(EventTypes.RoomTombstone)) ...{ if (room.canSendEvent(EventTypes.RoomTombstone)) ...{
Divider(thickness: 1), Divider(thickness: 1),
FutureBuilder<ServerCapabilities>( FutureBuilder<ServerCapabilities>(
future: room.client.requestServerCapabilities(), future: room.client.getCapabilities(),
builder: (context, snapshot) { builder: (context, snapshot) {
if (!snapshot.hasData) { if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator()); return Center(child: CircularProgressIndicator());

View File

@ -27,7 +27,7 @@ class SearchUI extends StatelessWidget {
} }
controller.publicRoomsResponse ??= Matrix.of(context) controller.publicRoomsResponse ??= Matrix.of(context)
.client .client
.searchPublicRooms( .queryPublicRooms(
server: server, server: server,
genericSearchTerm: controller.genericSearchTerm, genericSearchTerm: controller.genericSearchTerm,
) )

View File

@ -13,7 +13,7 @@ class Settings3PidUI extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
controller.request ??= controller.request ??=
Matrix.of(context).client.requestThirdPartyIdentifiers(); Matrix.of(context).client.getAccount3PIDs();
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
leading: BackButton(), leading: BackButton(),

View File

@ -89,7 +89,7 @@ class SettingsNotificationsUI extends StatelessWidget {
), ),
), ),
FutureBuilder<List<Pusher>>( FutureBuilder<List<Pusher>>(
future: Matrix.of(context).client.requestPushers(), future: Matrix.of(context).client.getPushers(),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasError) { if (snapshot.hasError) {
Center( Center(

View File

@ -25,7 +25,7 @@ class PublicRoomListItem extends StatelessWidget {
Future<String> _joinRoomAndWait(BuildContext context) async { Future<String> _joinRoomAndWait(BuildContext context) async {
final roomId = final roomId =
await Matrix.of(context).client.joinRoomOrAlias(publicRoomEntry.roomId); await Matrix.of(context).client.joinRoom(publicRoomEntry.roomId);
if (Matrix.of(context).client.getRoomById(roomId) == null) { if (Matrix.of(context).client.getRoomById(roomId) == null) {
await Matrix.of(context) await Matrix.of(context)
.client .client

View File

@ -1,7 +1,5 @@
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:logger/logger.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
class LogViewer extends StatefulWidget { class LogViewer extends StatefulWidget {
@override @override
@ -26,12 +24,10 @@ class _LogViewerState extends State<LogViewer> {
IconButton( IconButton(
icon: Icon(Icons.zoom_in_outlined), icon: Icon(Icons.zoom_in_outlined),
onPressed: () => setState(() => fontSize++), onPressed: () => setState(() => fontSize++),
tooltip: L10n.of(context).zoomIn,
), ),
IconButton( IconButton(
icon: Icon(Icons.zoom_out_outlined), icon: Icon(Icons.zoom_out_outlined),
onPressed: () => setState(() => fontSize--), onPressed: () => setState(() => fontSize--),
tooltip: L10n.of(context).zoomOut,
), ),
PopupMenuButton<Level>( PopupMenuButton<Level>(
itemBuilder: (context) => Level.values itemBuilder: (context) => Level.values
@ -48,141 +44,22 @@ class _LogViewerState extends State<LogViewer> {
itemCount: outputEvents.length, itemCount: outputEvents.length,
itemBuilder: (context, i) => SingleChildScrollView( itemBuilder: (context, i) => SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: RichText( child: Text(outputEvents[i].toDisplayString()),
text: TextSpan(
style: TextStyle(fontSize: fontSize),
children: _AnsiParser(outputEvents[i].lines.join('\n')).spans,
),
),
), ),
), ),
); );
} }
} }
class _AnsiParser { extension on LogEvent {
// ignore: constant_identifier_names String toDisplayString() {
static const TEXT = 0, BRACKET = 1, CODE = 2; var str = '# $title';
final String text; if (exception != null) {
str += ' - ${exception.toString()}';
Color foreground;
Color background;
List<TextSpan> spans;
_AnsiParser(this.text) {
final s = this.text;
spans = [];
var state = TEXT;
StringBuffer buffer;
final text = StringBuffer();
var code = 0;
List<int> codes;
// ignore: prefer_final_locals
for (var i = 0, n = s.length; i < n; i++) {
final c = s[i];
switch (state) {
case TEXT:
if (c == '\u001b') {
state = BRACKET;
buffer = StringBuffer(c);
code = 0;
codes = [];
} else {
text.write(c);
}
break;
case BRACKET:
buffer.write(c);
if (c == '[') {
state = CODE;
} else {
state = TEXT;
text.write(buffer);
}
break;
case CODE:
buffer.write(c);
final codeUnit = c.codeUnitAt(0);
if (codeUnit >= 48 && codeUnit <= 57) {
code = code * 10 + codeUnit - 48;
continue;
} else if (c == ';') {
codes.add(code);
code = 0;
continue;
} else {
if (text.isNotEmpty) {
spans.add(createSpan(text.toString()));
text.clear();
}
state = TEXT;
if (c == 'm') {
codes.add(code);
handleCodes(codes);
} else {
text.write(buffer);
}
}
break;
}
} }
if (stackTrace != null) {
spans.add(createSpan(text.toString())); str += '\n${stackTrace.toString()}';
}
void handleCodes(List<int> codes) {
if (codes.isEmpty) {
codes.add(0);
} }
return str;
switch (codes[0]) {
case 0:
foreground = getColor(0, true);
background = getColor(0, false);
break;
case 38:
foreground = getColor(codes[2], true);
break;
case 39:
foreground = getColor(0, true);
break;
case 48:
background = getColor(codes[2], false);
break;
case 49:
background = getColor(0, false);
}
}
Color getColor(int colorCode, bool foreground) {
switch (colorCode) {
case 0:
return foreground ? Colors.black : Colors.transparent;
case 12:
return Colors.lightBlue[300];
case 208:
return Colors.orange[300];
case 196:
return Colors.red[300];
case 199:
return Colors.pink[300];
default:
return Colors.white;
}
}
TextSpan createSpan(String text) {
return TextSpan(
text: text,
style: TextStyle(
color: foreground,
backgroundColor: background,
),
);
} }
} }

View File

@ -88,7 +88,7 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
final statusMsg = await store.getItem(SettingKeys.ownStatusMessage); final statusMsg = await store.getItem(SettingKeys.ownStatusMessage);
if (statusMsg?.isNotEmpty ?? false) { if (statusMsg?.isNotEmpty ?? false) {
Logs().v('Send cached status message: "$statusMsg"'); Logs().v('Send cached status message: "$statusMsg"');
await client.sendPresence( await client.setPresence(
client.userID, client.userID,
PresenceType.online, PresenceType.online,
statusMsg: statusMsg, statusMsg: statusMsg,

View File

@ -50,7 +50,7 @@ class MessageReactions extends StatelessWidget {
if (evt != null) { if (evt != null) {
showFutureLoadingDialog( showFutureLoadingDialog(
context: context, context: context,
future: () => evt.redact(), future: () => evt.redactEvent(),
); );
} }
} else { } else {

View File

@ -230,10 +230,10 @@ packages:
description: description:
path: "." path: "."
ref: main ref: main
resolved-ref: "70ee808911f49f34bbc933859dfffd4b804f1b2e" resolved-ref: "6fae2e1426fa440b09bafb9bc23e8791037c6efe"
url: "https://gitlab.com/famedly/famedlysdk.git" url: "https://gitlab.com/famedly/famedlysdk.git"
source: git source: git
version: "0.0.1" version: "0.1.0"
fcm_shared_isolate: fcm_shared_isolate:
dependency: "direct main" dependency: "direct main"
description: description:
@ -564,13 +564,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.0+1" version: "4.0.0+1"
logger:
dependency: transitive
description:
name: logger
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@ -598,7 +591,7 @@ packages:
name: matrix_api_lite name: matrix_api_lite
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.6" version: "0.3.1"
matrix_link_text: matrix_link_text:
dependency: transitive dependency: transitive
description: description: