Skip to content

Commit

Permalink
Merge pull request #1806 from nextcloud/add-gif-support
Browse files Browse the repository at this point in the history
Add gif support
  • Loading branch information
Ivansss authored Sep 20, 2024
2 parents db2f439 + 07dfa8b commit d2bc9f5
Show file tree
Hide file tree
Showing 12 changed files with 357 additions and 41 deletions.
29 changes: 29 additions & 0 deletions NextcloudTalk.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,10 @@
1FBC3BE92B61BD09003909E0 /* TestBaseRealm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FBC3BE82B61BD09003909E0 /* TestBaseRealm.swift */; };
1FC940B92A5F21FC00FFFADE /* SwiftMarkdownObjCBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0A1D432A5F1FA800A25433 /* SwiftMarkdownObjCBridge.swift */; };
1FC940BA2A5F21FD00FFFADE /* SwiftMarkdownObjCBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0A1D432A5F1FA800A25433 /* SwiftMarkdownObjCBridge.swift */; };
1FCE3D532C9B5918009C68A9 /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = 1FCE3D522C9B5918009C68A9 /* SwiftyGif */; };
1FCE3D552C9C189D009C68A9 /* NCChatFileControllerWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FCE3D542C9C189D009C68A9 /* NCChatFileControllerWrapper.swift */; };
1FCE3D572C9C4D18009C68A9 /* ReferenceGiphyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FCE3D562C9C4D18009C68A9 /* ReferenceGiphyView.swift */; };
1FCE3D592C9C4D21009C68A9 /* ReferenceGiphyView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1FCE3D582C9C4D21009C68A9 /* ReferenceGiphyView.xib */; };
1FD6F83C2B825069004048AB /* NCRoomsManagerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD6F83B2B825069004048AB /* NCRoomsManagerExtensions.swift */; };
1FD6F83E2B87B712004048AB /* NCUserStatusExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD6F83D2B87B712004048AB /* NCUserStatusExtensions.swift */; };
1FD8AE6B2A3A216300787C16 /* UIRoomTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD8AD8C2A3A162100787C16 /* UIRoomTest.swift */; };
Expand Down Expand Up @@ -781,6 +785,9 @@
1FB7B99B2BF0DF360093CE98 /* BannedActorCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BannedActorCell.xib; sourceTree = "<group>"; };
1FBC3BE42B61ACD5003909E0 /* UnitBaseChatViewControllerTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnitBaseChatViewControllerTest.swift; sourceTree = "<group>"; };
1FBC3BE82B61BD09003909E0 /* TestBaseRealm.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestBaseRealm.swift; sourceTree = "<group>"; };
1FCE3D542C9C189D009C68A9 /* NCChatFileControllerWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCChatFileControllerWrapper.swift; sourceTree = "<group>"; };
1FCE3D562C9C4D18009C68A9 /* ReferenceGiphyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReferenceGiphyView.swift; sourceTree = "<group>"; };
1FCE3D582C9C4D21009C68A9 /* ReferenceGiphyView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ReferenceGiphyView.xib; sourceTree = "<group>"; };
1FD6F83B2B825069004048AB /* NCRoomsManagerExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCRoomsManagerExtensions.swift; sourceTree = "<group>"; };
1FD6F83D2B87B712004048AB /* NCUserStatusExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCUserStatusExtensions.swift; sourceTree = "<group>"; };
1FD8AD8A2A3A162100787C16 /* NextcloudTalkUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudTalkUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -1233,6 +1240,7 @@
files = (
2CA1CCCA1F17C503002FE6A2 /* AudioToolbox.framework in Frameworks */,
2CCCD21D2835088F00F076CE /* OpenSSL in Frameworks */,
1FCE3D532C9B5918009C68A9 /* SwiftyGif in Frameworks */,
1F90EFC725FE4BE700F3FA55 /* IntentsUI.framework in Frameworks */,
2CA1CC971F016117002FE6A2 /* Security.framework in Frameworks */,
1F45A1212A01D8BA005FE87D /* SDWebImageSVGKitPlugin in Frameworks */,
Expand Down Expand Up @@ -1339,6 +1347,8 @@
1FDE7C9B28DE14B000CB718E /* ReferenceView.xib */,
1F46CE2828E05B3200E7D88E /* ReferenceDefaultView.swift */,
1F46CE2A28E05B3C00E7D88E /* ReferenceDefaultView.xib */,
1FCE3D562C9C4D18009C68A9 /* ReferenceGiphyView.swift */,
1FCE3D582C9C4D21009C68A9 /* ReferenceGiphyView.xib */,
1F24B5A128E0648600654457 /* ReferenceGithubView.swift */,
1F24B5A328E0649200654457 /* ReferenceGithubView.xib */,
1FFF41612C70937B00162F4D /* ReferenceZammadView.swift */,
Expand Down Expand Up @@ -2079,6 +2089,7 @@
2C42ADB320B58E6300296DEA /* NCChatController.m */,
1FEDE3C5257D439500853F79 /* NCChatFileController.h */,
1FEDE3C4257D439500853F79 /* NCChatFileController.m */,
1FCE3D542C9C189D009C68A9 /* NCChatFileControllerWrapper.swift */,
1FF4DA7F2C023FF300C1B952 /* NCChatFileStatus.swift */,
2C5BFBF0288A97D800E75118 /* NCPoll.h */,
2C5BFBF1288A97D800E75118 /* NCPoll.m */,
Expand Down Expand Up @@ -2236,6 +2247,7 @@
1FAB2E872ACD44D0001214EB /* WebRTC */,
80CDF8C32A8E098900CB57AE /* SwiftUIIntrospect */,
1F759C2B2B63CB93000534AB /* Realm */,
1FCE3D522C9B5918009C68A9 /* SwiftyGif */,
);
productName = NextcloudTalk;
productReference = 2C05747D1EDD9E8E00D9E7F2 /* NextcloudTalk.app */;
Expand Down Expand Up @@ -2393,6 +2405,7 @@
1FAB2E862ACD44CF001214EB /* XCRemoteSwiftPackageReference "talk-clients-webrtc" */,
80CDF8C22A8E098900CB57AE /* XCRemoteSwiftPackageReference "swiftui-introspect" */,
1F759C2A2B63CB93000534AB /* XCRemoteSwiftPackageReference "realm-swift-binary" */,
1FCE3D512C9B5918009C68A9 /* XCRemoteSwiftPackageReference "SwiftyGif" */,
);
productRefGroup = 2C05747E1EDD9E8E00D9E7F2 /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -2441,6 +2454,7 @@
2C330372255E6EBC00BDB4E4 /* InfoPlist.strings in Resources */,
2C78EFA11F828C41008AFA74 /* CallViewController.xib in Resources */,
2C3780C5210F4A26003F9AE8 /* HeaderWithButton.xib in Resources */,
1FCE3D592C9C4D21009C68A9 /* ReferenceGiphyView.xib in Resources */,
2C7F47AA20289B9600081CC7 /* Localizable.strings in Resources */,
2CB997C62A052449003C41AC /* EmojiAvatarPickerViewController.xib in Resources */,
2C0574A51EDDA2E300D9E7F2 /* LoginViewController.xib in Resources */,
Expand Down Expand Up @@ -2926,6 +2940,7 @@
1F66B72C29FA9414003FB168 /* SLKDefaultTypingIndicatorView.m in Sources */,
1F46CE2928E05B3200E7D88E /* ReferenceDefaultView.swift in Sources */,
2C444706265E59B100DF1DBC /* ShareConfirmationCollectionViewCell.m in Sources */,
1FCE3D552C9C189D009C68A9 /* NCChatFileControllerWrapper.swift in Sources */,
2C78EF991F80F81E008AFA74 /* NCSignalingController.m in Sources */,
2CB304202264775E0053078A /* UIResponder+SLKAdditions.m in Sources */,
2C8E2A1B232174C20022BFC9 /* MessageSeparatorTableViewCell.m in Sources */,
Expand Down Expand Up @@ -2981,6 +2996,7 @@
2C4446D32658147900DF1DBC /* TalkAccount.m in Sources */,
2CA1CCD61F1E664C002FE6A2 /* ContactsTableViewCell.m in Sources */,
1F77A6172AB9B161007B6037 /* ScreenCapturer.m in Sources */,
1FCE3D572C9C4D18009C68A9 /* ReferenceGiphyView.swift in Sources */,
1FFF41622C70937B00162F4D /* ReferenceZammadView.swift in Sources */,
1F98DF9C28E7484700E05174 /* ReferenceDeckView.swift in Sources */,
DA66582F27B6B19C00B46B11 /* UserProfileTableViewController+Actions.swift in Sources */,
Expand Down Expand Up @@ -4240,6 +4256,14 @@
revision = 5f15c82f4b02072c1595980580eade9325e1819f;
};
};
1FCE3D512C9B5918009C68A9 /* XCRemoteSwiftPackageReference "SwiftyGif" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/alexiscreuzot/SwiftyGif";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 5.4.5;
};
};
2CCCD21B2835088F00F076CE /* XCRemoteSwiftPackageReference "OpenSSL" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/krzyzanowskim/OpenSSL";
Expand Down Expand Up @@ -4449,6 +4473,11 @@
package = 1FAB2E862ACD44CF001214EB /* XCRemoteSwiftPackageReference "talk-clients-webrtc" */;
productName = WebRTC;
};
1FCE3D522C9B5918009C68A9 /* SwiftyGif */ = {
isa = XCSwiftPackageProductDependency;
package = 1FCE3D512C9B5918009C68A9 /* XCRemoteSwiftPackageReference "SwiftyGif" */;
productName = SwiftyGif;
};
1FF136192BFBC841006A6101 /* SwiftyAttributes */ = {
isa = XCSwiftPackageProductDependency;
package = 1F66B72D29FABD01003FB168 /* XCRemoteSwiftPackageReference "SwiftyAttributes" */;
Expand Down
100 changes: 71 additions & 29 deletions NextcloudTalk/BaseChatTableViewCell+File.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,18 +120,15 @@ extension BaseChatTableViewCell {

func requestPreview(for message: NCChatMessage, with account: TalkAccount) {
// Don't request a preview if we know that there's none
if !message.file().previewAvailable {
guard let file = message.file(), file.previewAvailable else {
self.showFallbackIcon(for: message)

return
}

let isVideoFile = NCUtils.isVideo(fileType: message.file().mimetype)
let isMediaFile = isVideoFile || NCUtils.isImage(fileType: message.file().mimetype)

// In case we can determine the height before requesting the preview, adjust the imageView constraints accordingly
if message.file().previewImageHeight > 0 {
self.filePreviewImageViewHeightConstraint?.constant = CGFloat(message.file().previewImageHeight)
if file.previewImageHeight > 0 {
self.filePreviewImageViewHeightConstraint?.constant = CGFloat(file.previewImageHeight)
} else {
let estimatedPreviewHeight = BaseChatTableViewCell.getEstimatedPreviewSize(for: message)

Expand All @@ -143,45 +140,90 @@ extension BaseChatTableViewCell {
self.filePreviewActivityIndicator?.isHidden = false
self.filePreviewActivityIndicator?.startAnimating()

let requestedHeight = Int(3 * fileMessageCellFileMaxPreviewHeight)
guard let previewRequest = NCAPIController.sharedInstance().createPreviewRequest(forFile: message.file().parameterId, withMaxHeight: requestedHeight, using: account) else { return }
if message.isAnimatableGif {
self.requestGifPreview(for: message, with: account)
} else {
self.requestDefaultPreview(for: message, with: account)
}
}

self.filePreviewImageView?.setImageWith(previewRequest, placeholderImage: nil, success: { [weak self] _, _, image in
guard let self, let imageView = self.filePreviewImageView else { return }
func requestGifPreview(for message: NCChatMessage, with account: TalkAccount) {
guard let fileId = message.file()?.parameterId else { return }

self.filePreviewActivityIndicator?.isHidden = true
self.filePreviewActivityIndicator?.stopAnimating()
let fileControllerWrapper = NCChatFileControllerWrapper()
self.fileControllerWrapper = fileControllerWrapper

let imageSize = CGSize(width: image.size.width * image.scale, height: image.size.height * image.scale)
let previewSize = BaseChatTableViewCell.getPreviewSize(from: imageSize, isMediaFile)
fileControllerWrapper.downloadFile(withFileId: fileId) { fileLocalPath in
// Check if we are still on the same cell
guard let cellMessage = self.message, let imageView = self.filePreviewImageView, cellMessage.file().parameterId == fileId
else {
// Different cell, don't do anything
return
}

if !previewSize.width.isFinite || !previewSize.height.isFinite {
self.showFallbackIcon(for: message)
guard let fileLocalPath, let data = try? Data(contentsOf: URL(fileURLWithPath: fileLocalPath)),
let gifImage = try? UIImage(gifData: data), let baseImage = UIImage(data: data) else {

// No gif, try to request a normal preview
self.requestDefaultPreview(for: message, with: account)
return
}

imageView.layer.borderColor = UIColor.secondarySystemFill.cgColor
imageView.layer.borderWidth = 1
imageView.setGifImage(gifImage)
self.adjustImageView(toImageSize: baseImage, ofMessage: message)
}
}

self.filePreviewImageViewHeightConstraint?.constant = previewSize.height
self.filePreviewImageViewWidthConstraint?.constant = previewSize.width
func requestDefaultPreview(for message: NCChatMessage, with account: TalkAccount) {
guard let file = message.file() else { return }

if isVideoFile {
// only show the play icon if there is an image preview (not on top of the default video placeholder)
self.filePreviewPlayIconImageView?.isHidden = false
// if the video preview is very narrow, make the play icon fit inside
self.filePreviewPlayIconImageView?.frame = CGRect(x: 0, y: 0, width: min(min(previewSize.height, previewSize.width), fileMessageCellVideoPlayIconSize), height: min(min(previewSize.height, previewSize.width), fileMessageCellVideoPlayIconSize))
self.filePreviewPlayIconImageView?.center = CGPoint(x: previewSize.width / 2.0, y: previewSize.height / 2.0)
}
let requestedHeight = Int(3 * fileMessageCellFileMaxPreviewHeight)
guard let previewRequest = NCAPIController.sharedInstance().createPreviewRequest(forFile: file.parameterId, withMaxHeight: requestedHeight, using: account) else { return }

imageView.image = image
self.filePreviewImageView?.setImageWith(previewRequest, placeholderImage: nil, success: { [weak self] _, _, image in
guard let self, let imageView = self.filePreviewImageView else { return }

self.delegate?.cellHasDownloadedImagePreview(withHeight: ceil(previewSize.height), for: message)
imageView.image = image
self.adjustImageView(toImageSize: image, ofMessage: message)
}, failure: { _, _, _ in
self.showFallbackIcon(for: message)
})
}

func adjustImageView(toImageSize image: UIImage, ofMessage message: NCChatMessage) {
guard let imageView = self.filePreviewImageView, let file = message.file() else { return }

let isVideoFile = NCUtils.isVideo(fileType: file.mimetype)
let isMediaFile = isVideoFile || NCUtils.isImage(fileType: file.mimetype)

self.filePreviewActivityIndicator?.isHidden = true
self.filePreviewActivityIndicator?.stopAnimating()

let imageSize = CGSize(width: image.size.width * image.scale, height: image.size.height * image.scale)
let previewSize = BaseChatTableViewCell.getPreviewSize(from: imageSize, isMediaFile)

if !previewSize.width.isFinite || !previewSize.height.isFinite {
self.showFallbackIcon(for: message)
return
}

imageView.layer.borderColor = UIColor.secondarySystemFill.cgColor
imageView.layer.borderWidth = 1

self.filePreviewImageViewHeightConstraint?.constant = previewSize.height
self.filePreviewImageViewWidthConstraint?.constant = previewSize.width

if isVideoFile {
// only show the play icon if there is an image preview (not on top of the default video placeholder)
self.filePreviewPlayIconImageView?.isHidden = false
// if the video preview is very narrow, make the play icon fit inside
self.filePreviewPlayIconImageView?.frame = CGRect(x: 0, y: 0, width: min(min(previewSize.height, previewSize.width), fileMessageCellVideoPlayIconSize), height: min(min(previewSize.height, previewSize.width), fileMessageCellVideoPlayIconSize))
self.filePreviewPlayIconImageView?.center = CGPoint(x: previewSize.width / 2.0, y: previewSize.height / 2.0)
}

self.delegate?.cellHasDownloadedImagePreview(withHeight: ceil(previewSize.height), for: message)
}

func showFallbackIcon(for message: NCChatMessage) {
let imageName = NCUtils.previewImage(forMimeType: message.file().mimetype)

Expand Down
2 changes: 2 additions & 0 deletions NextcloudTalk/BaseChatTableViewCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//

import MapKit
import SwiftyGif

protocol BaseChatTableViewCellDelegate: AnyObject {

Expand Down Expand Up @@ -78,6 +79,7 @@ class BaseChatTableViewCell: UITableViewCell, ReactionsViewDelegate {
internal var fileActivityIndicator: MDCActivityIndicator?
internal var filePreviewActivityIndicator: MDCActivityIndicator?
internal var filePreviewPlayIconImageView: UIImageView?
internal var fileControllerWrapper: NCChatFileControllerWrapper?

// Location cell
internal var locationPreviewImageView: UIImageView?
Expand Down
28 changes: 28 additions & 0 deletions NextcloudTalk/NCChatFileControllerWrapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
// SPDX-License-Identifier: GPL-3.0-or-later
//

import Foundation

public class NCChatFileControllerWrapper: NSObject, NCChatFileControllerDelegate {

let fileController = NCChatFileController()
var completionBlock: ((_ fileLocalPath: String?) -> Void)?

public func downloadFile(withFileId fileId: String, completionBlock: @escaping (_ fileLocalPath: String?) -> Void) {
self.completionBlock = completionBlock

fileController.delegate = self
fileController.downloadFile(withFileId: fileId)
}

public func fileControllerDidLoadFile(_ fileController: NCChatFileController, with fileStatus: NCChatFileStatus) {
self.completionBlock?(fileStatus.fileLocalPath)
}

public func fileControllerDidFailLoadingFile(_ fileController: NCChatFileController, withErrorDescription errorDescription: String) {
self.completionBlock?(nil)
}

}
11 changes: 11 additions & 0 deletions NextcloudTalk/NCChatMessage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -257,4 +257,15 @@ import SwiftyAttributes

return nil
}

public var isAnimatableGif: Bool {
guard let accountId, let file = self.file() else { return false }

let capabilities = NCDatabaseManager.sharedInstance().serverCapabilities(forAccountId: accountId)

guard NCUtils.isGif(fileType: file.mimetype), let maxGifSize = capabilities?.maxGifSize, maxGifSize > 0 else { return false }

return file.size <= maxGifSize
}

}
9 changes: 8 additions & 1 deletion NextcloudTalk/NCDatabaseManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

NSString *const kTalkDatabaseFolder = @"Library/Application Support/Talk";
NSString *const kTalkDatabaseFileName = @"talk.realm";
uint64_t const kTalkDatabaseSchemaVersion = 67;
uint64_t const kTalkDatabaseSchemaVersion = 68;

NSString * const kCapabilitySystemMessages = @"system-messages";
NSString * const kCapabilityNotificationLevels = @"notification-levels";
Expand Down Expand Up @@ -454,6 +454,13 @@ - (void)setTalkCapabilities:(NSDictionary *)capabilitiesDict onTalkCapabilitiesO
} else {
capabilities.federationOnlyTrustedServers = NO;
}

NSDictionary *previewConfig = [talkConfig objectForKey:@"previews"];
NSArray *previewConfigKeys = [previewConfig allKeys];

if ([previewConfigKeys containsObject:@"max-gif-size"]) {
capabilities.maxGifSize = [[previewConfig objectForKey:@"max-gif-size"] intValue];
}
}

#pragma mark - Federated capabilities
Expand Down
Loading

0 comments on commit d2bc9f5

Please sign in to comment.