Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve shared item list #1437

Merged
merged 8 commits into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 45 additions & 16 deletions NextcloudTalk/BaseChatViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,9 @@ import QuickLook
NCUserInterfaceController.sharedInstance().numberOfAllocatedChatViewControllers += 1
}

public convenience init?(for room: NCRoom, withMessage messages: [NCChatMessage]) {
// Not using an optional here, because it is not available from ObjC
// Pass "0" as highlightMessageId to not highlight a message
public convenience init?(for room: NCRoom, withMessage messages: [NCChatMessage], withHighlightId highlightMessageId: Int) {
self.init(for: room)

// When we pass in a fixed number of messages, we hide the inputbar by default
Expand All @@ -213,18 +215,12 @@ import QuickLook
self.tableView?.slk_scrollToBottom(animated: false)

self.appendMessages(messages: messages)
self.tableView?.reloadData()
}

// Not using an optional here, because it is not available from ObjC
public convenience init?(for room: NCRoom, withMessage messages: [NCChatMessage], withHighlightId highlightMessageId: Int) {
self.init(for: room, withMessage: messages)

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
if let (indexPath, _) = self.indexPathAndMessage(forMessageId: highlightMessageId) {
self.highlightMessage(at: indexPath, with: .middle)
}
}
self.tableView?.performBatchUpdates({
self.tableView?.reloadData()
}, completion: { _ in
self.highlightMessageWithContentOffset(messageId: highlightMessageId)
})
}

required init?(coder decoder: NSCoder) {
Expand Down Expand Up @@ -2550,15 +2546,21 @@ import QuickLook
}

if let message = self.message(for: indexPath) {
var width = tableView.frame.width - kChatCellAvatarHeight
width -= tableView.safeAreaInsets.left + tableView.safeAreaInsets.right

return self.getCellHeight(for: message, with: width)
return self.getCellHeight(for: message)
}

return kChatMessageCellMinimumHeight
}

func getCellHeight(for message: NCChatMessage) -> CGFloat {
guard let tableView = self.tableView else { return kChatMessageCellMinimumHeight }

var width = tableView.frame.width - kChatCellAvatarHeight
width -= tableView.safeAreaInsets.left + tableView.safeAreaInsets.right

return self.getCellHeight(for: message, with: width)
}

// swiftlint:disable:next cyclomatic_complexity
func getCellHeight(for message: NCChatMessage, with originalWidth: CGFloat) -> CGFloat {
// Chat separators
Expand Down Expand Up @@ -3006,6 +3008,33 @@ import QuickLook
}
}

internal func highlightMessageWithContentOffset(messageId: Int) {
guard messageId > 0,
let tableView = self.tableView,
let (indexPath, _) = self.indexPathAndMessage(forMessageId: messageId)
else { return }

self.highlightMessage(at: indexPath, with: .none)

let rect = tableView.rectForRow(at: indexPath)

// ContentOffset when the cell is at the top of the tableView
let contentOffsetTop = rect.origin.y - tableView.safeAreaInsets.top

// ContentOffset when the cell is at the middle of the tableView
let contentOffsetMiddle = contentOffsetTop - tableView.frame.height / 2 + rect.height / 2

// Fallback to the top offset in case the top of the cell would be scrolled outside of the view
let newContentOffset = min(contentOffsetTop, contentOffsetMiddle)

tableView.contentOffset.y = newContentOffset
}

public func reloadDataAndHighlightMessage(messageId: Int) {
self.tableView?.reloadData()
self.highlightMessageWithContentOffset(messageId: messageId)
}

func showNewMessagesView(until message: NCChatMessage) {
self.firstUnreadMessage = message
self.unreadMessageButton.isHidden = false
Expand Down
2 changes: 2 additions & 0 deletions NextcloudTalk/NCAPIController.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ typedef void (^DeleteChatMessageCompletionBlock)(NSDictionary *messageDict, NSEr
typedef void (^ClearChatHistoryCompletionBlock)(NSDictionary *messageDict, NSError *error, NSInteger statusCode);
typedef void (^GetSharedItemsOverviewCompletionBlock)(NSDictionary *sharedItemsOverview, NSError *error, NSInteger statusCode);
typedef void (^GetSharedItemsCompletionBlock)(NSArray *sharedItems, NSInteger lastKnownMessage, NSError *error, NSInteger statusCode);
typedef void (^GetMessageContextInRoomCompletionBlock)(NSArray *messages, NSError *error, NSInteger statusCode);

typedef void (^GetTranslationsCompletionBlock)(NSArray *languages, BOOL languageDetection, NSError *error, NSInteger statusCode);
typedef void (^MessageTranslationCompletionBlock)(NSDictionary *translationDict, NSError *error, NSInteger statusCode);
Expand Down Expand Up @@ -224,6 +225,7 @@ extern NSInteger const kReceivedChatMessagesLimit;
- (NSURLSessionDataTask *)markChatAsUnreadInRoom:(NSString *)token forAccount:(TalkAccount *)account withCompletionBlock:(SendChatMessagesCompletionBlock)block;
- (NSURLSessionDataTask *)getSharedItemsOverviewInRoom:(NSString *)token withLimit:(NSInteger)limit forAccount:(TalkAccount *)account withCompletionBlock:(GetSharedItemsOverviewCompletionBlock)block;
- (NSURLSessionDataTask *)getSharedItemsOfType:(NSString *)objectType fromLastMessageId:(NSInteger)messageId withLimit:(NSInteger)limit inRoom:(NSString *)token forAccount:(TalkAccount *)account withCompletionBlock:(GetSharedItemsCompletionBlock)block;
- (NSURLSessionDataTask *)getMessageContextInRoom:(NSString *)token forMessageId:(NSInteger)messageId withLimit:(NSInteger)limit forAccount:(TalkAccount *)account withCompletionBlock:(GetMessageContextInRoomCompletionBlock)block;

// Translations
- (NSURLSessionDataTask *)getAvailableTranslationsForAccount:(TalkAccount *)account withCompletionBlock:(GetTranslationsCompletionBlock)block;
Expand Down
34 changes: 34 additions & 0 deletions NextcloudTalk/NCAPIController.m
Original file line number Diff line number Diff line change
Expand Up @@ -1587,6 +1587,7 @@ - (NSURLSessionDataTask *)getSharedItemsOverviewInRoom:(NSString *)token withLim

return task;
}

- (NSURLSessionDataTask *)getSharedItemsOfType:(NSString *)objectType fromLastMessageId:(NSInteger)messageId withLimit:(NSInteger)limit inRoom:(NSString *)token forAccount:(TalkAccount *)account withCompletionBlock:(GetSharedItemsCompletionBlock)block
{
NSString *encodedToken = [token stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]];
Expand Down Expand Up @@ -1637,6 +1638,39 @@ - (NSURLSessionDataTask *)getSharedItemsOfType:(NSString *)objectType fromLastMe
return task;
}

- (NSURLSessionDataTask *)getMessageContextInRoom:(NSString *)token forMessageId:(NSInteger)messageId withLimit:(NSInteger)limit forAccount:(TalkAccount *)account withCompletionBlock:(GetMessageContextInRoomCompletionBlock)block
{
NSString *encodedToken = [token stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]];
NSString *endpoint = [NSString stringWithFormat:@"chat/%@/%ld/context", encodedToken, (long)messageId];
NSInteger chatAPIVersion = [self chatAPIVersionForAccount:account];
NSString *URLString = [self getRequestURLForEndpoint:endpoint withAPIVersion:chatAPIVersion forAccount:account];

NSMutableDictionary *parameters = [[NSMutableDictionary alloc] init];

if (limit && limit > 0) {
// Limit is optional server-side and defaults to 50, maximum is 100
[parameters setObject:@(limit) forKey:@"limit"];
}

NCAPISessionManager *apiSessionManager = [_apiSessionManagers objectForKey:account.accountId];

NSURLSessionDataTask *task = [apiSessionManager GET:URLString parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSArray *responseMessages = [[responseObject objectForKey:@"ocs"] objectForKey:@"data"];

if (block) {
block(responseMessages, nil, 0);
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSInteger statusCode = [self getResponseStatusCode:task.response];
[self checkResponseStatusCode:statusCode forAccount:account];
if (block) {
block(nil, error, statusCode);
}
}];

return task;
}

#pragma mark - Translations Controller

- (NSURLSessionDataTask *)getAvailableTranslationsForAccount:(TalkAccount *)account withCompletionBlock:(GetTranslationsCompletionBlock)block
Expand Down
2 changes: 2 additions & 0 deletions NextcloudTalk/NCChatController.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#import "NCChatMessage.h"

typedef void (^UpdateHistoryInBackgroundCompletionBlock)(NSError *error);
typedef void (^GetMessagesContextCompletionBlock)(NSArray<NCChatMessage *> * _Nullable messages);

@class NCRoom;

Expand Down Expand Up @@ -65,5 +66,6 @@ extern NSString * const NCChatControllerDidReceiveMessagesInBackgroundNotificati
- (void)removeExpiredMessages;
- (BOOL)hasHistoryFromMessageId:(NSInteger)messageId;
- (void)storeMessages:(NSArray *)messages withRealm:(RLMRealm *)realm;
- (void)getMessageContextForMessageId:(NSInteger)messageId withLimit:(NSInteger)limit withCompletionBlock:(GetMessagesContextCompletionBlock)block;

@end
35 changes: 35 additions & 0 deletions NextcloudTalk/NCChatController.m
Original file line number Diff line number Diff line change
Expand Up @@ -814,4 +814,39 @@ - (BOOL)hasHistoryFromMessageId:(NSInteger)messageId
return YES;
}

- (void)getMessageContextForMessageId:(NSInteger)messageId withLimit:(NSInteger)limit withCompletionBlock:(GetMessagesContextCompletionBlock)block
{
[[NCAPIController sharedInstance] getMessageContextInRoom:self.room.token forMessageId:messageId withLimit:limit forAccount:self.account withCompletionBlock:^(NSArray *messages, NSError *error, NSInteger statusCode) {
if (error) {
if (block) {
block(nil);
}

return;
}

NSMutableArray *chatMessages = [[NSMutableArray alloc] initWithCapacity:messages.count];

for (NSDictionary *messageDict in messages) {
NCChatMessage *message = [NCChatMessage messageWithDictionary:messageDict andAccountId:self.account.accountId];
[chatMessages addObject:message];

if (!message.file) {
continue;
}

// Try to get the stored preview height from our database, when the message is already stored
NCChatMessage *managedMessage = [NCChatMessage objectsWhere:@"internalId = %@", message.internalId].firstObject;

if (managedMessage && managedMessage.file && managedMessage.file.previewImageHeight > 0) {
message.file.previewImageHeight = managedMessage.file.previewImageHeight;
}
}

if (block) {
block(chatMessages);
}
}];
}

@end
84 changes: 58 additions & 26 deletions NextcloudTalk/RoomSharedItemsTableViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,30 +73,6 @@ import QuickLook
self.getItemsOverview()
}

func presentItemTypeSelector() {
let itemTypesActionSheet = UIAlertController(title: NSLocalizedString("Shared items", comment: ""), message: nil, preferredStyle: .actionSheet)

for itemType in availableItemTypes() {
let itemTypeName = nameForItemType(itemType: itemType)
let action = UIAlertAction(title: itemTypeName, style: .default) { _ in
self.setupViewForItemType(itemType: itemType)
}

if itemType == currentItemType {
action.setValue(UIImage(named: "checkmark")?.withRenderingMode(_: .alwaysOriginal), forKey: "image")
}
itemTypesActionSheet.addAction(action)
}

itemTypesActionSheet.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: nil))

// Presentation on iPads
itemTypesActionSheet.popoverPresentationController?.sourceView = self.navigationItem.titleView
itemTypesActionSheet.popoverPresentationController?.sourceRect = self.navigationItem.titleView?.frame ?? CGRect()

self.present(itemTypesActionSheet, animated: true, completion: nil)
}

func availableItemTypes() -> [String] {
var availableItemTypes: [String] = []
for itemType in sharedItemsOverview.keys {
Expand Down Expand Up @@ -183,8 +159,25 @@ import QuickLook
itemTypeSelectorButton.setTitle(buttonTitle, for: .normal)
itemTypeSelectorButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .medium)
itemTypeSelectorButton.setTitleColor(NCAppBranding.themeTextColor(), for: .normal)
itemTypeSelectorButton.addTarget(self, action: #selector(presentItemTypeSelector), for: .touchUpInside)
self.navigationItem.titleView = itemTypeSelectorButton

var menuActions: [UIAction] = []

for itemType in availableItemTypes() {
let itemTypeName = nameForItemType(itemType: itemType)
let action = UIAction(title: itemTypeName, image: nil) { [unowned self] _ in
self.setupViewForItemType(itemType: itemType)
}

if itemType == currentItemType {
action.state = .on
}

menuActions.append(action)
}

itemTypeSelectorButton.showsMenuAsPrimaryAction = true
itemTypeSelectorButton.menu = UIMenu(children: menuActions)
}

func showFetchingItemsPlaceholderView() {
Expand Down Expand Up @@ -383,7 +376,12 @@ import QuickLook

let message = currentItems[indexPath.row]

cell.fileNameLabel?.text = message.parsedMessage().string
if let file = message.file() {
cell.fileNameLabel?.text = file.name
} else {
cell.fileNameLabel?.text = message.parsedMessage().string
}

var infoLabelText = NCUtils.relativeTimeFromDate(date: Date(timeIntervalSince1970: Double(message.timestamp)))
if !message.actorDisplayName.isEmpty {
infoLabelText += " ⸱ " + message.actorDisplayName
Expand All @@ -409,6 +407,40 @@ import QuickLook
return cell
}

weak var previewChatViewController: BaseChatViewController?

override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
return UIContextMenuConfiguration(identifier: indexPath as NSCopying, previewProvider: {

// Init the BaseChatViewController without message to directly show a preview
if let chatViewController = BaseChatViewController(for: self.room, withMessage: [], withHighlightId: 0) {
self.previewChatViewController = chatViewController

// Fetch the context of the message and update the BaseChatViewController
let message = self.currentItems[indexPath.row]
NCChatController(for: self.room).getMessageContext(forMessageId: message.messageId, withLimit: 50) { messages in
guard let messages else { return }

chatViewController.appendMessages(messages: messages)
chatViewController.reloadDataAndHighlightMessage(messageId: message.messageId)
}

let navController = NCNavigationController(rootViewController: chatViewController)
return navController
}

return nil
}, actionProvider: nil)
}

override func tableView(_ tableView: UITableView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
animator.addAnimations {
if let previewChatViewController = self.previewChatViewController {
self.navigationController?.pushViewController(previewChatViewController, animated: false)
}
}
}

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as? DirectoryTableViewCell ?? DirectoryTableViewCell()
let message = currentItems[indexPath.row]
Expand Down
13 changes: 3 additions & 10 deletions NextcloudTalk/RoomSharedItemsTableViewController.xib
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="22155" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22131"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
Expand All @@ -15,21 +14,15 @@
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableView opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" bouncesZoom="NO" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="i5M-Pr-FkT">
<tableView opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" bouncesZoom="NO" style="insetGrouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="18" id="i5M-Pr-FkT">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<viewLayoutGuide key="safeArea" id="vLr-E1-eTs"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<connections>
<outlet property="dataSource" destination="-1" id="Tng-2m-Rnh"/>
<outlet property="delegate" destination="-1" id="9aC-8N-iBw"/>
</connections>
<point key="canvasLocation" x="139" y="98"/>
</tableView>
</objects>
<resources>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>
Loading