[Client] Implement thumbnails
This commit is contained in:
parent
28dee0e2e3
commit
81c12c81f2
|
@ -488,6 +488,7 @@ class Client {
|
|||
|
||||
/// Uploads a new user avatar for this user.
|
||||
Future<void> setAvatar(MatrixFile file) async {
|
||||
await file.resize(width: 128);
|
||||
final uploadResp = await upload(file);
|
||||
await jsonRequest(
|
||||
type: HTTPType.PUT,
|
||||
|
|
|
@ -429,31 +429,50 @@ class Event {
|
|||
return;
|
||||
}
|
||||
|
||||
bool get hasThumbnail =>
|
||||
content['info'] is Map<String, dynamic> &&
|
||||
(content['info'].containsKey('thumbnail_url') ||
|
||||
content['info'].containsKey('thumbnail_file'));
|
||||
|
||||
/// Downloads (and decryptes if necessary) the attachment of this
|
||||
/// event and returns it as a [MatrixFile]. If this event doesn't
|
||||
/// contain an attachment, this throws an error.
|
||||
Future<MatrixFile> downloadAndDecryptAttachment() async {
|
||||
/// contain an attachment, this throws an error. Set [getThumbnail] to
|
||||
/// true to download the thumbnail instead.
|
||||
Future<MatrixFile> downloadAndDecryptAttachment(
|
||||
{bool getThumbnail = false}) async {
|
||||
if (![EventTypes.Message, EventTypes.Sticker].contains(type)) {
|
||||
throw ("This event has the type '$typeKey' and so it can't contain an attachment.");
|
||||
}
|
||||
if (!content.containsKey('url') && !content.containsKey('file')) {
|
||||
if (!getThumbnail &&
|
||||
!content.containsKey('url') &&
|
||||
!content.containsKey('file')) {
|
||||
throw ("This event hasn't any attachment.");
|
||||
}
|
||||
final isEncrypted = !content.containsKey('url');
|
||||
if (getThumbnail && !hasThumbnail) {
|
||||
throw ("This event hasn't any thumbnail.");
|
||||
}
|
||||
final isEncrypted = getThumbnail
|
||||
? !content['info'].containsKey('thumbnail_url')
|
||||
: !content.containsKey('url');
|
||||
|
||||
if (isEncrypted && !room.client.encryptionEnabled) {
|
||||
throw ('Encryption is not enabled in your Client.');
|
||||
}
|
||||
var mxContent =
|
||||
MxContent(isEncrypted ? content['file']['url'] : content['url']);
|
||||
var mxContent = getThumbnail
|
||||
? MxContent(isEncrypted
|
||||
? content['info']['thumbnail_file']['url']
|
||||
: content['info']['thumbnail_url'])
|
||||
: MxContent(isEncrypted ? content['file']['url'] : content['url']);
|
||||
|
||||
Uint8List uint8list;
|
||||
|
||||
// Is this file storeable?
|
||||
final infoMap =
|
||||
getThumbnail ? content['info']['thumbnail_info'] : content['info'];
|
||||
final storeable = room.client.storeAPI.extended &&
|
||||
content['info'] is Map<String, dynamic> &&
|
||||
content['info']['size'] is int &&
|
||||
content['info']['size'] <= ExtendedStoreAPI.MAX_FILE_SIZE;
|
||||
infoMap is Map<String, dynamic> &&
|
||||
infoMap['size'] is int &&
|
||||
infoMap['size'] <= ExtendedStoreAPI.MAX_FILE_SIZE;
|
||||
|
||||
if (storeable) {
|
||||
uint8list = await room.client.store.getFile(mxContent.mxc);
|
||||
|
@ -470,15 +489,16 @@ class Event {
|
|||
|
||||
// Decrypt the file
|
||||
if (isEncrypted) {
|
||||
if (!content.containsKey('file') ||
|
||||
!content['file']['key']['key_ops'].contains('decrypt')) {
|
||||
final fileMap =
|
||||
getThumbnail ? content['info']['thumbnail_file'] : content['file'];
|
||||
if (!fileMap['key']['key_ops'].contains('decrypt')) {
|
||||
throw ("Missing 'decrypt' in 'key_ops'.");
|
||||
}
|
||||
final encryptedFile = EncryptedFile();
|
||||
encryptedFile.data = uint8list;
|
||||
encryptedFile.iv = content['file']['iv'];
|
||||
encryptedFile.k = content['file']['key']['k'];
|
||||
encryptedFile.sha256 = content['file']['hashes']['sha256'];
|
||||
encryptedFile.iv = fileMap['iv'];
|
||||
encryptedFile.k = fileMap['key']['k'];
|
||||
encryptedFile.sha256 = fileMap['hashes']['sha256'];
|
||||
uint8list = await decryptFile(encryptedFile);
|
||||
}
|
||||
return MatrixFile(bytes: uint8list, path: '/$body');
|
||||
|
|
|
@ -34,6 +34,7 @@ import 'package:famedlysdk/src/utils/matrix_exception.dart';
|
|||
import 'package:famedlysdk/src/utils/matrix_file.dart';
|
||||
import 'package:famedlysdk/src/utils/mx_content.dart';
|
||||
import 'package:famedlysdk/src/utils/session_key.dart';
|
||||
import 'package:image/image.dart';
|
||||
import 'package:matrix_file_e2ee/matrix_file_e2ee.dart';
|
||||
import 'package:mime_type/mime_type.dart';
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
|
@ -478,17 +479,13 @@ class Room {
|
|||
Map<String, dynamic> info,
|
||||
bool waitUntilSent = false,
|
||||
}) async {
|
||||
var fileName = file.path.split('/').last;
|
||||
final sendEncrypted = encrypted && client.fileEncryptionEnabled;
|
||||
EncryptedFile encryptedFile;
|
||||
if (sendEncrypted) {
|
||||
encryptedFile = await file.encrypt();
|
||||
}
|
||||
final uploadResp = await client.upload(
|
||||
file,
|
||||
contentType: sendEncrypted ? 'application/octet-stream' : null,
|
||||
);
|
||||
Image fileImage;
|
||||
Image thumbnailImage;
|
||||
MatrixFile thumbnail;
|
||||
EncryptedFile encryptedThumbnail;
|
||||
String thumbnailUploadResp;
|
||||
|
||||
var fileName = file.path.split('/').last;
|
||||
final mimeType = mime(file.path) ?? '';
|
||||
if (msgType == null) {
|
||||
final metaType = (mimeType).split('/')[0];
|
||||
|
@ -504,6 +501,35 @@ class Room {
|
|||
}
|
||||
}
|
||||
|
||||
if (msgType == 'm.image') {
|
||||
var thumbnailPathParts = file.path.split('/');
|
||||
thumbnailPathParts.last = 'thumbnail_' + thumbnailPathParts.last;
|
||||
final thumbnailPath = thumbnailPathParts.join('/');
|
||||
thumbnail = MatrixFile(bytes: file.bytes, path: thumbnailPath);
|
||||
await thumbnail.resize(width: 512);
|
||||
fileImage = decodeImage(file.bytes.toList());
|
||||
thumbnailImage = decodeImage(thumbnail.bytes.toList());
|
||||
}
|
||||
|
||||
final sendEncrypted = encrypted && client.fileEncryptionEnabled;
|
||||
EncryptedFile encryptedFile;
|
||||
if (sendEncrypted) {
|
||||
encryptedFile = await file.encrypt();
|
||||
if (thumbnail != null) {
|
||||
encryptedThumbnail = await thumbnail.encrypt();
|
||||
}
|
||||
}
|
||||
final uploadResp = await client.upload(
|
||||
file,
|
||||
contentType: sendEncrypted ? 'application/octet-stream' : null,
|
||||
);
|
||||
if (thumbnail != null) {
|
||||
thumbnailUploadResp = await client.upload(
|
||||
thumbnail,
|
||||
contentType: sendEncrypted ? 'application/octet-stream' : null,
|
||||
);
|
||||
}
|
||||
|
||||
// Send event
|
||||
var content = <String, dynamic>{
|
||||
'msgtype': msgType,
|
||||
|
@ -529,6 +555,32 @@ class Room {
|
|||
{
|
||||
'mimetype': mimeType,
|
||||
'size': file.size,
|
||||
if (fileImage != null) 'h': fileImage.height,
|
||||
if (fileImage != null) 'w': fileImage.width,
|
||||
if (thumbnailUploadResp != null && !sendEncrypted)
|
||||
'thumbnail_url': thumbnailUploadResp,
|
||||
if (thumbnailUploadResp != null && sendEncrypted)
|
||||
'thumbnail_file': {
|
||||
'url': thumbnailUploadResp,
|
||||
'mimetype': mimeType,
|
||||
'v': 'v2',
|
||||
'key': {
|
||||
'alg': 'A256CTR',
|
||||
'ext': true,
|
||||
'k': encryptedThumbnail.k,
|
||||
'key_ops': ['encrypt', 'decrypt'],
|
||||
'kty': 'oct'
|
||||
},
|
||||
'iv': encryptedThumbnail.iv,
|
||||
'hashes': {'sha256': encryptedThumbnail.sha256}
|
||||
},
|
||||
if (thumbnailImage != null)
|
||||
'thumbnail_info': {
|
||||
'h': thumbnailImage.height,
|
||||
'mimetype': mimeType,
|
||||
'size': thumbnailImage.length,
|
||||
'w': thumbnailImage.width,
|
||||
}
|
||||
}
|
||||
};
|
||||
final sendResponse = sendEvent(
|
||||
|
@ -1198,6 +1250,7 @@ class Room {
|
|||
/// Uploads a new user avatar for this room. Returns the event ID of the new
|
||||
/// m.room.avatar event.
|
||||
Future<String> setAvatar(MatrixFile file) async {
|
||||
await file.resize(width: 256);
|
||||
final uploadResp = await client.upload(file);
|
||||
final setAvatarResp = await client.jsonRequest(
|
||||
type: HTTPType.PUT,
|
||||
|
|
|
@ -2,12 +2,31 @@
|
|||
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:image/image.dart';
|
||||
import 'package:matrix_file_e2ee/matrix_file_e2ee.dart';
|
||||
|
||||
class MatrixFile {
|
||||
Uint8List bytes;
|
||||
String path;
|
||||
|
||||
/// If this file is an Image, this will resize it to the
|
||||
/// given width and height. Otherwise returns false.
|
||||
/// At least width or height must be set!
|
||||
Future<bool> resize({int width, int height}) async {
|
||||
if (width == null && height == null) {
|
||||
throw ('At least width or height must be set!');
|
||||
}
|
||||
Image image;
|
||||
try {
|
||||
image = decodeImage(bytes);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
final resizedImage = copyResize(image, width: width, height: height);
|
||||
bytes = encodePng(resizedImage);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Encrypts this file, changes the [bytes] and returns the
|
||||
/// encryption information as an [EncryptedFile].
|
||||
Future<EncryptedFile> encrypt() async {
|
||||
|
|
28
pubspec.lock
28
pubspec.lock
|
@ -8,6 +8,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.36.3"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.13"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -211,6 +218,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
image:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.12"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -334,6 +348,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.9.0"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
pointycastle:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -516,6 +537,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.13"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.7.0"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -11,6 +11,7 @@ dependencies:
|
|||
http: ^0.12.0+4
|
||||
mime_type: ^0.2.4
|
||||
canonical_json: ^1.0.0
|
||||
image: ^2.1.4
|
||||
|
||||
olm:
|
||||
git:
|
||||
|
|
Loading…
Reference in a new issue