Skip to content

Commit d3174dd

Browse files
authored
Merge pull request #1437 from nextcloud/improve-shared-item-list
Improve shared item list
2 parents b8997df + c1498c5 commit d3174dd

7 files changed

Lines changed: 179 additions & 52 deletions

NextcloudTalk/BaseChatViewController.swift

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,9 @@ import QuickLook
202202
NCUserInterfaceController.sharedInstance().numberOfAllocatedChatViewControllers += 1
203203
}
204204

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

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

215217
self.appendMessages(messages: messages)
216-
self.tableView?.reloadData()
217-
}
218-
219-
// Not using an optional here, because it is not available from ObjC
220-
public convenience init?(for room: NCRoom, withMessage messages: [NCChatMessage], withHighlightId highlightMessageId: Int) {
221-
self.init(for: room, withMessage: messages)
222218

223-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
224-
if let (indexPath, _) = self.indexPathAndMessage(forMessageId: highlightMessageId) {
225-
self.highlightMessage(at: indexPath, with: .middle)
226-
}
227-
}
219+
self.tableView?.performBatchUpdates({
220+
self.tableView?.reloadData()
221+
}, completion: { _ in
222+
self.highlightMessageWithContentOffset(messageId: highlightMessageId)
223+
})
228224
}
229225

230226
required init?(coder decoder: NSCoder) {
@@ -2550,15 +2546,21 @@ import QuickLook
25502546
}
25512547

25522548
if let message = self.message(for: indexPath) {
2553-
var width = tableView.frame.width - kChatCellAvatarHeight
2554-
width -= tableView.safeAreaInsets.left + tableView.safeAreaInsets.right
2555-
2556-
return self.getCellHeight(for: message, with: width)
2549+
return self.getCellHeight(for: message)
25572550
}
25582551

25592552
return kChatMessageCellMinimumHeight
25602553
}
25612554

2555+
func getCellHeight(for message: NCChatMessage) -> CGFloat {
2556+
guard let tableView = self.tableView else { return kChatMessageCellMinimumHeight }
2557+
2558+
var width = tableView.frame.width - kChatCellAvatarHeight
2559+
width -= tableView.safeAreaInsets.left + tableView.safeAreaInsets.right
2560+
2561+
return self.getCellHeight(for: message, with: width)
2562+
}
2563+
25622564
// swiftlint:disable:next cyclomatic_complexity
25632565
func getCellHeight(for message: NCChatMessage, with originalWidth: CGFloat) -> CGFloat {
25642566
// Chat separators
@@ -3006,6 +3008,33 @@ import QuickLook
30063008
}
30073009
}
30083010

3011+
internal func highlightMessageWithContentOffset(messageId: Int) {
3012+
guard messageId > 0,
3013+
let tableView = self.tableView,
3014+
let (indexPath, _) = self.indexPathAndMessage(forMessageId: messageId)
3015+
else { return }
3016+
3017+
self.highlightMessage(at: indexPath, with: .none)
3018+
3019+
let rect = tableView.rectForRow(at: indexPath)
3020+
3021+
// ContentOffset when the cell is at the top of the tableView
3022+
let contentOffsetTop = rect.origin.y - tableView.safeAreaInsets.top
3023+
3024+
// ContentOffset when the cell is at the middle of the tableView
3025+
let contentOffsetMiddle = contentOffsetTop - tableView.frame.height / 2 + rect.height / 2
3026+
3027+
// Fallback to the top offset in case the top of the cell would be scrolled outside of the view
3028+
let newContentOffset = min(contentOffsetTop, contentOffsetMiddle)
3029+
3030+
tableView.contentOffset.y = newContentOffset
3031+
}
3032+
3033+
public func reloadDataAndHighlightMessage(messageId: Int) {
3034+
self.tableView?.reloadData()
3035+
self.highlightMessageWithContentOffset(messageId: messageId)
3036+
}
3037+
30093038
func showNewMessagesView(until message: NCChatMessage) {
30103039
self.firstUnreadMessage = message
30113040
self.unreadMessageButton.isHidden = false

NextcloudTalk/NCAPIController.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ typedef void (^DeleteChatMessageCompletionBlock)(NSDictionary *messageDict, NSEr
7272
typedef void (^ClearChatHistoryCompletionBlock)(NSDictionary *messageDict, NSError *error, NSInteger statusCode);
7373
typedef void (^GetSharedItemsOverviewCompletionBlock)(NSDictionary *sharedItemsOverview, NSError *error, NSInteger statusCode);
7474
typedef void (^GetSharedItemsCompletionBlock)(NSArray *sharedItems, NSInteger lastKnownMessage, NSError *error, NSInteger statusCode);
75+
typedef void (^GetMessageContextInRoomCompletionBlock)(NSArray *messages, NSError *error, NSInteger statusCode);
7576

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

228230
// Translations
229231
- (NSURLSessionDataTask *)getAvailableTranslationsForAccount:(TalkAccount *)account withCompletionBlock:(GetTranslationsCompletionBlock)block;

NextcloudTalk/NCAPIController.m

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1587,6 +1587,7 @@ - (NSURLSessionDataTask *)getSharedItemsOverviewInRoom:(NSString *)token withLim
15871587

15881588
return task;
15891589
}
1590+
15901591
- (NSURLSessionDataTask *)getSharedItemsOfType:(NSString *)objectType fromLastMessageId:(NSInteger)messageId withLimit:(NSInteger)limit inRoom:(NSString *)token forAccount:(TalkAccount *)account withCompletionBlock:(GetSharedItemsCompletionBlock)block
15911592
{
15921593
NSString *encodedToken = [token stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]];
@@ -1637,6 +1638,39 @@ - (NSURLSessionDataTask *)getSharedItemsOfType:(NSString *)objectType fromLastMe
16371638
return task;
16381639
}
16391640

1641+
- (NSURLSessionDataTask *)getMessageContextInRoom:(NSString *)token forMessageId:(NSInteger)messageId withLimit:(NSInteger)limit forAccount:(TalkAccount *)account withCompletionBlock:(GetMessageContextInRoomCompletionBlock)block
1642+
{
1643+
NSString *encodedToken = [token stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]];
1644+
NSString *endpoint = [NSString stringWithFormat:@"chat/%@/%ld/context", encodedToken, (long)messageId];
1645+
NSInteger chatAPIVersion = [self chatAPIVersionForAccount:account];
1646+
NSString *URLString = [self getRequestURLForEndpoint:endpoint withAPIVersion:chatAPIVersion forAccount:account];
1647+
1648+
NSMutableDictionary *parameters = [[NSMutableDictionary alloc] init];
1649+
1650+
if (limit && limit > 0) {
1651+
// Limit is optional server-side and defaults to 50, maximum is 100
1652+
[parameters setObject:@(limit) forKey:@"limit"];
1653+
}
1654+
1655+
NCAPISessionManager *apiSessionManager = [_apiSessionManagers objectForKey:account.accountId];
1656+
1657+
NSURLSessionDataTask *task = [apiSessionManager GET:URLString parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
1658+
NSArray *responseMessages = [[responseObject objectForKey:@"ocs"] objectForKey:@"data"];
1659+
1660+
if (block) {
1661+
block(responseMessages, nil, 0);
1662+
}
1663+
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
1664+
NSInteger statusCode = [self getResponseStatusCode:task.response];
1665+
[self checkResponseStatusCode:statusCode forAccount:account];
1666+
if (block) {
1667+
block(nil, error, statusCode);
1668+
}
1669+
}];
1670+
1671+
return task;
1672+
}
1673+
16401674
#pragma mark - Translations Controller
16411675

16421676
- (NSURLSessionDataTask *)getAvailableTranslationsForAccount:(TalkAccount *)account withCompletionBlock:(GetTranslationsCompletionBlock)block

NextcloudTalk/NCChatController.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#import "NCChatMessage.h"
2727

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

3031
@class NCRoom;
3132

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

6971
@end

NextcloudTalk/NCChatController.m

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,4 +814,39 @@ - (BOOL)hasHistoryFromMessageId:(NSInteger)messageId
814814
return YES;
815815
}
816816

817+
- (void)getMessageContextForMessageId:(NSInteger)messageId withLimit:(NSInteger)limit withCompletionBlock:(GetMessagesContextCompletionBlock)block
818+
{
819+
[[NCAPIController sharedInstance] getMessageContextInRoom:self.room.token forMessageId:messageId withLimit:limit forAccount:self.account withCompletionBlock:^(NSArray *messages, NSError *error, NSInteger statusCode) {
820+
if (error) {
821+
if (block) {
822+
block(nil);
823+
}
824+
825+
return;
826+
}
827+
828+
NSMutableArray *chatMessages = [[NSMutableArray alloc] initWithCapacity:messages.count];
829+
830+
for (NSDictionary *messageDict in messages) {
831+
NCChatMessage *message = [NCChatMessage messageWithDictionary:messageDict andAccountId:self.account.accountId];
832+
[chatMessages addObject:message];
833+
834+
if (!message.file) {
835+
continue;
836+
}
837+
838+
// Try to get the stored preview height from our database, when the message is already stored
839+
NCChatMessage *managedMessage = [NCChatMessage objectsWhere:@"internalId = %@", message.internalId].firstObject;
840+
841+
if (managedMessage && managedMessage.file && managedMessage.file.previewImageHeight > 0) {
842+
message.file.previewImageHeight = managedMessage.file.previewImageHeight;
843+
}
844+
}
845+
846+
if (block) {
847+
block(chatMessages);
848+
}
849+
}];
850+
}
851+
817852
@end

NextcloudTalk/RoomSharedItemsTableViewController.swift

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -73,30 +73,6 @@ import QuickLook
7373
self.getItemsOverview()
7474
}
7575

76-
func presentItemTypeSelector() {
77-
let itemTypesActionSheet = UIAlertController(title: NSLocalizedString("Shared items", comment: ""), message: nil, preferredStyle: .actionSheet)
78-
79-
for itemType in availableItemTypes() {
80-
let itemTypeName = nameForItemType(itemType: itemType)
81-
let action = UIAlertAction(title: itemTypeName, style: .default) { _ in
82-
self.setupViewForItemType(itemType: itemType)
83-
}
84-
85-
if itemType == currentItemType {
86-
action.setValue(UIImage(named: "checkmark")?.withRenderingMode(_: .alwaysOriginal), forKey: "image")
87-
}
88-
itemTypesActionSheet.addAction(action)
89-
}
90-
91-
itemTypesActionSheet.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: nil))
92-
93-
// Presentation on iPads
94-
itemTypesActionSheet.popoverPresentationController?.sourceView = self.navigationItem.titleView
95-
itemTypesActionSheet.popoverPresentationController?.sourceRect = self.navigationItem.titleView?.frame ?? CGRect()
96-
97-
self.present(itemTypesActionSheet, animated: true, completion: nil)
98-
}
99-
10076
func availableItemTypes() -> [String] {
10177
var availableItemTypes: [String] = []
10278
for itemType in sharedItemsOverview.keys {
@@ -183,8 +159,25 @@ import QuickLook
183159
itemTypeSelectorButton.setTitle(buttonTitle, for: .normal)
184160
itemTypeSelectorButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .medium)
185161
itemTypeSelectorButton.setTitleColor(NCAppBranding.themeTextColor(), for: .normal)
186-
itemTypeSelectorButton.addTarget(self, action: #selector(presentItemTypeSelector), for: .touchUpInside)
187162
self.navigationItem.titleView = itemTypeSelectorButton
163+
164+
var menuActions: [UIAction] = []
165+
166+
for itemType in availableItemTypes() {
167+
let itemTypeName = nameForItemType(itemType: itemType)
168+
let action = UIAction(title: itemTypeName, image: nil) { [unowned self] _ in
169+
self.setupViewForItemType(itemType: itemType)
170+
}
171+
172+
if itemType == currentItemType {
173+
action.state = .on
174+
}
175+
176+
menuActions.append(action)
177+
}
178+
179+
itemTypeSelectorButton.showsMenuAsPrimaryAction = true
180+
itemTypeSelectorButton.menu = UIMenu(children: menuActions)
188181
}
189182

190183
func showFetchingItemsPlaceholderView() {
@@ -383,7 +376,12 @@ import QuickLook
383376

384377
let message = currentItems[indexPath.row]
385378

386-
cell.fileNameLabel?.text = message.parsedMessage().string
379+
if let file = message.file() {
380+
cell.fileNameLabel?.text = file.name
381+
} else {
382+
cell.fileNameLabel?.text = message.parsedMessage().string
383+
}
384+
387385
var infoLabelText = NCUtils.relativeTimeFromDate(date: Date(timeIntervalSince1970: Double(message.timestamp)))
388386
if !message.actorDisplayName.isEmpty {
389387
infoLabelText += "" + message.actorDisplayName
@@ -409,6 +407,40 @@ import QuickLook
409407
return cell
410408
}
411409

410+
weak var previewChatViewController: BaseChatViewController?
411+
412+
override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
413+
return UIContextMenuConfiguration(identifier: indexPath as NSCopying, previewProvider: {
414+
415+
// Init the BaseChatViewController without message to directly show a preview
416+
if let chatViewController = BaseChatViewController(for: self.room, withMessage: [], withHighlightId: 0) {
417+
self.previewChatViewController = chatViewController
418+
419+
// Fetch the context of the message and update the BaseChatViewController
420+
let message = self.currentItems[indexPath.row]
421+
NCChatController(for: self.room).getMessageContext(forMessageId: message.messageId, withLimit: 50) { messages in
422+
guard let messages else { return }
423+
424+
chatViewController.appendMessages(messages: messages)
425+
chatViewController.reloadDataAndHighlightMessage(messageId: message.messageId)
426+
}
427+
428+
let navController = NCNavigationController(rootViewController: chatViewController)
429+
return navController
430+
}
431+
432+
return nil
433+
}, actionProvider: nil)
434+
}
435+
436+
override func tableView(_ tableView: UITableView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
437+
animator.addAnimations {
438+
if let previewChatViewController = self.previewChatViewController {
439+
self.navigationController?.pushViewController(previewChatViewController, animated: false)
440+
}
441+
}
442+
}
443+
412444
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
413445
let cell = tableView.cellForRow(at: indexPath) as? DirectoryTableViewCell ?? DirectoryTableViewCell()
414446
let message = currentItems[indexPath.row]
Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<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">
2+
<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">
33
<device id="retina6_1" orientation="portrait" appearance="light"/>
44
<dependencies>
55
<deployment identifier="iOS"/>
6-
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
6+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22131"/>
77
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
8-
<capability name="System colors in document resources" minToolsVersion="11.0"/>
98
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
109
</dependencies>
1110
<objects>
@@ -15,21 +14,15 @@
1514
</connections>
1615
</placeholder>
1716
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
18-
<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">
17+
<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">
1918
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
2019
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
2120
<viewLayoutGuide key="safeArea" id="vLr-E1-eTs"/>
22-
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
2321
<connections>
2422
<outlet property="dataSource" destination="-1" id="Tng-2m-Rnh"/>
2523
<outlet property="delegate" destination="-1" id="9aC-8N-iBw"/>
2624
</connections>
2725
<point key="canvasLocation" x="139" y="98"/>
2826
</tableView>
2927
</objects>
30-
<resources>
31-
<systemColor name="systemBackgroundColor">
32-
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
33-
</systemColor>
34-
</resources>
3528
</document>

0 commit comments

Comments
 (0)