pop view if message is deleted
- use global ui database connection
This commit is contained in:
parent
1afc251401
commit
fabd3996c2
|
@ -123,6 +123,7 @@ typedef enum : NSUInteger {
|
|||
ConversationViewCellDelegate,
|
||||
ConversationInputTextViewDelegate,
|
||||
MessageActionsDelegate,
|
||||
MessageDetailViewDelegate,
|
||||
MenuActionsViewControllerDelegate,
|
||||
OWSMessageBubbleViewDelegate,
|
||||
UICollectionViewDelegate,
|
||||
|
@ -1924,6 +1925,14 @@ typedef enum : NSUInteger {
|
|||
[self populateReplyForViewItem:conversationViewItem];
|
||||
}
|
||||
|
||||
#pragma mark - MessageDetailViewDelegate
|
||||
|
||||
- (void)detailViewMessageWasDeleted:(MessageDetailViewController *)messageDetailViewController
|
||||
{
|
||||
OWSLogInfo(@"");
|
||||
[self.navigationController popToViewController:self animated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - MenuActionsViewControllerDelegate
|
||||
|
||||
- (void)menuActionsDidHide:(MenuActionsViewController *)menuActionsViewController
|
||||
|
@ -2158,7 +2167,6 @@ typedef enum : NSUInteger {
|
|||
|
||||
MediaGallery *mediaGallery =
|
||||
[[MediaGallery alloc] initWithThread:self.thread
|
||||
uiDatabaseConnection:self.uiDatabaseConnection
|
||||
options:MediaGalleryOptionSliderEnabled | MediaGalleryOptionShowAllMediaButton];
|
||||
|
||||
[mediaGallery presentDetailViewFromViewController:self mediaAttachment:attachmentStream replacingView:imageView];
|
||||
|
@ -2181,7 +2189,6 @@ typedef enum : NSUInteger {
|
|||
|
||||
MediaGallery *mediaGallery =
|
||||
[[MediaGallery alloc] initWithThread:self.thread
|
||||
uiDatabaseConnection:self.uiDatabaseConnection
|
||||
options:MediaGalleryOptionSliderEnabled | MediaGalleryOptionShowAllMediaButton];
|
||||
|
||||
[mediaGallery presentDetailViewFromViewController:self mediaAttachment:attachmentStream replacingView:imageView];
|
||||
|
@ -2358,12 +2365,13 @@ typedef enum : NSUInteger {
|
|||
OWSAssertDebug([conversationItem.interaction isKindOfClass:[TSMessage class]]);
|
||||
|
||||
TSMessage *message = (TSMessage *)conversationItem.interaction;
|
||||
MessageDetailViewController *view =
|
||||
MessageDetailViewController *detailVC =
|
||||
[[MessageDetailViewController alloc] initWithViewItem:conversationItem
|
||||
message:message
|
||||
thread:self.thread
|
||||
mode:MessageMetadataViewModeFocusOnMetadata];
|
||||
[self.navigationController pushViewController:view animated:YES];
|
||||
detailVC.delegate = self;
|
||||
[self.navigationController pushViewController:detailVC animated:YES];
|
||||
}
|
||||
|
||||
- (void)populateReplyForViewItem:(id<ConversationViewItem>)conversationItem
|
||||
|
|
|
@ -73,8 +73,8 @@ class ConversationSearchViewController: UITableViewController, BlockListCacheDel
|
|||
tableView.register(ContactTableViewCell.self, forCellReuseIdentifier: ContactTableViewCell.reuseIdentifier())
|
||||
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(yapDatabaseModified),
|
||||
name: NSNotification.Name.YapDatabaseModified,
|
||||
selector: #selector(uiDatabaseModified),
|
||||
name: .OWSUIDatabaseConnectionDidUpdate,
|
||||
object: OWSPrimaryStorage.shared().dbNotificationObject)
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(themeDidChange),
|
||||
|
@ -101,7 +101,7 @@ class ConversationSearchViewController: UITableViewController, BlockListCacheDel
|
|||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
@objc internal func yapDatabaseModified(notification: NSNotification) {
|
||||
@objc internal func uiDatabaseModified(notification: NSNotification) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
refreshSearchResults()
|
||||
|
|
|
@ -229,11 +229,11 @@ protocol MediaGalleryDataSource: class {
|
|||
func showAllMedia(focusedItem: MediaGalleryItem)
|
||||
func dismissMediaDetailViewController(_ mediaDetailViewController: MediaPageViewController, animated isAnimated: Bool, completion: (() -> Void)?)
|
||||
|
||||
func delete(items: [MediaGalleryItem], initiatedBy: MediaGalleryDataSourceDelegate)
|
||||
func delete(items: [MediaGalleryItem], initiatedBy: AnyObject)
|
||||
}
|
||||
|
||||
protocol MediaGalleryDataSourceDelegate: class {
|
||||
func mediaGalleryDataSource(_ mediaGalleryDataSource: MediaGalleryDataSource, willDelete items: [MediaGalleryItem], initiatedBy: MediaGalleryDataSourceDelegate)
|
||||
func mediaGalleryDataSource(_ mediaGalleryDataSource: MediaGalleryDataSource, willDelete items: [MediaGalleryItem], initiatedBy: AnyObject)
|
||||
func mediaGalleryDataSource(_ mediaGalleryDataSource: MediaGalleryDataSource, deletedSections: IndexSet, deletedItems: [IndexPath])
|
||||
}
|
||||
|
||||
|
@ -318,7 +318,10 @@ class MediaGallery: NSObject, MediaGalleryDataSource, MediaTileViewControllerDel
|
|||
|
||||
private var pageViewController: MediaPageViewController?
|
||||
|
||||
private let uiDatabaseConnection: YapDatabaseConnection
|
||||
private var uiDatabaseConnection: YapDatabaseConnection {
|
||||
return OWSPrimaryStorage.shared().uiDatabaseConnection
|
||||
}
|
||||
|
||||
private let editingDatabaseConnection: YapDatabaseConnection
|
||||
private let mediaGalleryFinder: OWSMediaGalleryFinder
|
||||
|
||||
|
@ -334,16 +337,19 @@ class MediaGallery: NSObject, MediaGalleryDataSource, MediaTileViewControllerDel
|
|||
}
|
||||
|
||||
@objc
|
||||
init(thread: TSThread, uiDatabaseConnection: YapDatabaseConnection, options: MediaGalleryOption = []) {
|
||||
init(thread: TSThread, options: MediaGalleryOption = []) {
|
||||
self.thread = thread
|
||||
assert(uiDatabaseConnection.isInLongLivedReadTransaction())
|
||||
self.uiDatabaseConnection = uiDatabaseConnection
|
||||
|
||||
self.editingDatabaseConnection = OWSPrimaryStorage.shared().newDatabaseConnection()
|
||||
|
||||
self.options = options
|
||||
self.mediaGalleryFinder = OWSMediaGalleryFinder(thread: thread)
|
||||
super.init()
|
||||
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(uiDatabaseDidUpdate),
|
||||
name: .OWSUIDatabaseConnectionDidUpdate,
|
||||
object: OWSPrimaryStorage.shared().dbNotificationObject)
|
||||
}
|
||||
|
||||
// MARK: Present/Dismiss
|
||||
|
@ -709,7 +715,70 @@ class MediaGallery: NSObject, MediaGalleryDataSource, MediaTileViewControllerDel
|
|||
]
|
||||
}
|
||||
|
||||
// MARK: MediaGalleryDataSource
|
||||
// MARK: - Database Notifications
|
||||
|
||||
@objc
|
||||
func uiDatabaseDidUpdate(notification: Notification) {
|
||||
guard let notifications = notification.userInfo?[OWSUIDatabaseConnectionNotificationsKey] as? [Notification] else {
|
||||
owsFailDebug("notifications was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
guard mediaGalleryFinder.hasMediaChanges(in: notifications, dbConnection: uiDatabaseConnection) else {
|
||||
Logger.verbose("no changes for thread: \(thread)")
|
||||
return
|
||||
}
|
||||
|
||||
let rowChanges = extractRowChanges(notifications: notifications)
|
||||
assert(rowChanges.count > 0)
|
||||
|
||||
process(rowChanges: rowChanges)
|
||||
}
|
||||
|
||||
func extractRowChanges(notifications: [Notification]) -> [YapDatabaseViewRowChange] {
|
||||
return notifications.flatMap { notification -> [YapDatabaseViewRowChange] in
|
||||
guard let userInfo = notification.userInfo else {
|
||||
owsFailDebug("userInfo was unexpectedly nil")
|
||||
return []
|
||||
}
|
||||
|
||||
guard let extensionChanges = userInfo["extensions"] as? [AnyHashable: Any] else {
|
||||
owsFailDebug("extensionChanges was unexpectedly nil")
|
||||
return []
|
||||
}
|
||||
|
||||
guard let galleryData = extensionChanges[OWSMediaGalleryFinder.databaseExtensionName()] as? [AnyHashable: Any] else {
|
||||
owsFailDebug("galleryData was unexpectedly nil")
|
||||
return []
|
||||
}
|
||||
|
||||
guard let galleryChanges = galleryData["changes"] as? [Any] else {
|
||||
owsFailDebug("gallerlyChanges was unexpectedly nil")
|
||||
return []
|
||||
}
|
||||
|
||||
return galleryChanges.compactMap { $0 as? YapDatabaseViewRowChange }
|
||||
}
|
||||
}
|
||||
|
||||
func process(rowChanges: [YapDatabaseViewRowChange]) {
|
||||
let deleteChanges = rowChanges.filter { $0.type == .delete }
|
||||
|
||||
let deletedItems: [MediaGalleryItem] = deleteChanges.compactMap { (deleteChange: YapDatabaseViewRowChange) -> MediaGalleryItem? in
|
||||
guard let deletedItem = self.galleryItems.first(where: { galleryItem in
|
||||
galleryItem.attachmentStream.uniqueId == deleteChange.collectionKey.key
|
||||
}) else {
|
||||
Logger.debug("deletedItem was never loaded - no need to remove.")
|
||||
return nil
|
||||
}
|
||||
|
||||
return deletedItem
|
||||
}
|
||||
|
||||
self.delete(items: deletedItems, initiatedBy: self)
|
||||
}
|
||||
|
||||
// MARK: - MediaGalleryDataSource
|
||||
|
||||
lazy var mediaTileViewController: MediaTileViewController = {
|
||||
let vc = MediaTileViewController(mediaGalleryDataSource: self, uiDatabaseConnection: self.uiDatabaseConnection)
|
||||
|
@ -777,6 +846,10 @@ class MediaGallery: NSObject, MediaGalleryDataSource, MediaTileViewControllerDel
|
|||
}
|
||||
}
|
||||
|
||||
enum MediaGalleryError: Error {
|
||||
case itemNoLongerExists
|
||||
}
|
||||
|
||||
func ensureGalleryItemsLoaded(_ direction: GalleryDirection, item: MediaGalleryItem, amount: UInt, completion: ((IndexSet, [IndexPath]) -> Void)? = nil ) {
|
||||
|
||||
var galleryItems: [MediaGalleryItem] = self.galleryItems
|
||||
|
@ -786,92 +859,102 @@ class MediaGallery: NSObject, MediaGalleryDataSource, MediaTileViewControllerDel
|
|||
var newGalleryItems: [MediaGalleryItem] = []
|
||||
var newDates: [GalleryDate] = []
|
||||
|
||||
Bench(title: "fetching gallery items") {
|
||||
self.uiDatabaseConnection.read { transaction in
|
||||
do {
|
||||
try Bench(title: "fetching gallery items") {
|
||||
try self.uiDatabaseConnection.read { transaction in
|
||||
guard let index = self.mediaGalleryFinder.mediaIndex(attachment: item.attachmentStream, transaction: transaction) else {
|
||||
throw MediaGalleryError.itemNoLongerExists
|
||||
}
|
||||
let initialIndex: Int = index.intValue
|
||||
let mediaCount: Int = Int(self.mediaGalleryFinder.mediaCount(transaction: transaction))
|
||||
|
||||
let initialIndex: Int = Int(self.mediaGalleryFinder.mediaIndex(attachment: item.attachmentStream, transaction: transaction))
|
||||
let mediaCount: Int = Int(self.mediaGalleryFinder.mediaCount(transaction: transaction))
|
||||
let requestRange: Range<Int> = { () -> Range<Int> in
|
||||
let range: Range<Int> = { () -> Range<Int> in
|
||||
switch direction {
|
||||
case .around:
|
||||
// To keep it simple, this isn't exactly *amount* sized if `message` window overlaps the end or
|
||||
// beginning of the view. Still, we have sufficient buffer to fetch more as the user swipes.
|
||||
let start: Int = initialIndex - Int(amount) / 2
|
||||
let end: Int = initialIndex + Int(amount) / 2
|
||||
|
||||
let requestRange: Range<Int> = { () -> Range<Int> in
|
||||
let range: Range<Int> = { () -> Range<Int> in
|
||||
switch direction {
|
||||
case .around:
|
||||
// To keep it simple, this isn't exactly *amount* sized if `message` window overlaps the end or
|
||||
// beginning of the view. Still, we have sufficient buffer to fetch more as the user swipes.
|
||||
let start: Int = initialIndex - Int(amount) / 2
|
||||
let end: Int = initialIndex + Int(amount) / 2
|
||||
return start..<end
|
||||
case .before:
|
||||
let start: Int = initialIndex - Int(amount)
|
||||
let end: Int = initialIndex
|
||||
|
||||
return start..<end
|
||||
case .before:
|
||||
let start: Int = initialIndex - Int(amount)
|
||||
let end: Int = initialIndex
|
||||
return start..<end
|
||||
case .after:
|
||||
let start: Int = initialIndex
|
||||
let end: Int = initialIndex + Int(amount)
|
||||
|
||||
return start..<end
|
||||
case .after:
|
||||
let start: Int = initialIndex
|
||||
let end: Int = initialIndex + Int(amount)
|
||||
return start..<end
|
||||
}
|
||||
}()
|
||||
|
||||
return start..<end
|
||||
}
|
||||
return range.clamped(to: 0..<mediaCount)
|
||||
}()
|
||||
|
||||
return range.clamped(to: 0..<mediaCount)
|
||||
}()
|
||||
|
||||
let requestSet = IndexSet(integersIn: requestRange)
|
||||
guard !self.fetchedIndexSet.contains(integersIn: requestSet) else {
|
||||
Logger.debug("all requested messages have already been loaded.")
|
||||
return
|
||||
}
|
||||
|
||||
let unfetchedSet = requestSet.subtracting(self.fetchedIndexSet)
|
||||
|
||||
// For perf we only want to fetch a substantially full batch...
|
||||
let isSubstantialRequest = unfetchedSet.count > (requestSet.count / 2)
|
||||
// ...but we always fulfill even small requests if we're getting just the tail end of a gallery.
|
||||
let isFetchingEdgeOfGallery = (self.fetchedIndexSet.count - unfetchedSet.count) < requestSet.count
|
||||
|
||||
guard isSubstantialRequest || isFetchingEdgeOfGallery else {
|
||||
Logger.debug("ignoring small fetch request: \(unfetchedSet.count)")
|
||||
return
|
||||
}
|
||||
|
||||
Logger.debug("fetching set: \(unfetchedSet)")
|
||||
let nsRange: NSRange = NSRange(location: unfetchedSet.min()!, length: unfetchedSet.count)
|
||||
self.mediaGalleryFinder.enumerateMediaAttachments(range: nsRange, transaction: transaction) { (attachment: TSAttachment) in
|
||||
|
||||
guard !self.deletedAttachments.contains(attachment) else {
|
||||
Logger.debug("skipping \(attachment) which has been deleted.")
|
||||
let requestSet = IndexSet(integersIn: requestRange)
|
||||
guard !self.fetchedIndexSet.contains(integersIn: requestSet) else {
|
||||
Logger.debug("all requested messages have already been loaded.")
|
||||
return
|
||||
}
|
||||
|
||||
guard let item: MediaGalleryItem = self.buildGalleryItem(attachment: attachment, transaction: transaction) else {
|
||||
owsFailDebug("unexpectedly failed to buildGalleryItem")
|
||||
let unfetchedSet = requestSet.subtracting(self.fetchedIndexSet)
|
||||
|
||||
// For perf we only want to fetch a substantially full batch...
|
||||
let isSubstantialRequest = unfetchedSet.count > (requestSet.count / 2)
|
||||
// ...but we always fulfill even small requests if we're getting just the tail end of a gallery.
|
||||
let isFetchingEdgeOfGallery = (self.fetchedIndexSet.count - unfetchedSet.count) < requestSet.count
|
||||
|
||||
guard isSubstantialRequest || isFetchingEdgeOfGallery else {
|
||||
Logger.debug("ignoring small fetch request: \(unfetchedSet.count)")
|
||||
return
|
||||
}
|
||||
|
||||
let date = item.galleryDate
|
||||
Logger.debug("fetching set: \(unfetchedSet)")
|
||||
let nsRange: NSRange = NSRange(location: unfetchedSet.min()!, length: unfetchedSet.count)
|
||||
self.mediaGalleryFinder.enumerateMediaAttachments(range: nsRange, transaction: transaction) { (attachment: TSAttachment) in
|
||||
|
||||
galleryItems.append(item)
|
||||
if sections[date] != nil {
|
||||
sections[date]!.append(item)
|
||||
guard !self.deletedAttachments.contains(attachment) else {
|
||||
Logger.debug("skipping \(attachment) which has been deleted.")
|
||||
return
|
||||
}
|
||||
|
||||
// so we can update collectionView
|
||||
newGalleryItems.append(item)
|
||||
} else {
|
||||
sectionDates.append(date)
|
||||
sections[date] = [item]
|
||||
guard let item: MediaGalleryItem = self.buildGalleryItem(attachment: attachment, transaction: transaction) else {
|
||||
owsFailDebug("unexpectedly failed to buildGalleryItem")
|
||||
return
|
||||
}
|
||||
|
||||
// so we can update collectionView
|
||||
newDates.append(date)
|
||||
newGalleryItems.append(item)
|
||||
let date = item.galleryDate
|
||||
|
||||
galleryItems.append(item)
|
||||
if sections[date] != nil {
|
||||
sections[date]!.append(item)
|
||||
|
||||
// so we can update collectionView
|
||||
newGalleryItems.append(item)
|
||||
} else {
|
||||
sectionDates.append(date)
|
||||
sections[date] = [item]
|
||||
|
||||
// so we can update collectionView
|
||||
newDates.append(date)
|
||||
newGalleryItems.append(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.fetchedIndexSet = self.fetchedIndexSet.union(unfetchedSet)
|
||||
self.hasFetchedOldest = self.fetchedIndexSet.min() == 0
|
||||
self.hasFetchedMostRecent = self.fetchedIndexSet.max() == mediaCount - 1
|
||||
self.fetchedIndexSet = self.fetchedIndexSet.union(unfetchedSet)
|
||||
self.hasFetchedOldest = self.fetchedIndexSet.min() == 0
|
||||
self.hasFetchedMostRecent = self.fetchedIndexSet.max() == mediaCount - 1
|
||||
}
|
||||
}
|
||||
} catch MediaGalleryError.itemNoLongerExists {
|
||||
Logger.debug("Ignoring reload, since item no longer exists.")
|
||||
return
|
||||
} catch {
|
||||
owsFailDebug("unexpected error: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
// TODO only sort if changed
|
||||
|
@ -919,7 +1002,7 @@ class MediaGallery: NSObject, MediaGalleryDataSource, MediaTileViewControllerDel
|
|||
dataSourceDelegates.append(Weak(value: dataSourceDelegate))
|
||||
}
|
||||
|
||||
func delete(items: [MediaGalleryItem], initiatedBy: MediaGalleryDataSourceDelegate) {
|
||||
func delete(items: [MediaGalleryItem], initiatedBy: AnyObject) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
Logger.info("with items: \(items.map { ($0.attachmentStream, $0.message.timestamp) })")
|
||||
|
|
|
@ -401,11 +401,11 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
|||
|
||||
// MARK: MediaGalleryDataSourceDelegate
|
||||
|
||||
func mediaGalleryDataSource(_ mediaGalleryDataSource: MediaGalleryDataSource, willDelete items: [MediaGalleryItem], initiatedBy: MediaGalleryDataSourceDelegate) {
|
||||
func mediaGalleryDataSource(_ mediaGalleryDataSource: MediaGalleryDataSource, willDelete items: [MediaGalleryItem], initiatedBy: AnyObject) {
|
||||
Logger.debug("")
|
||||
|
||||
guard let currentItem = self.currentItem else {
|
||||
owsFailDebug("currentItem was unexpectedly nil")
|
||||
owsFailDebug("currentItem was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
@ -610,7 +610,7 @@ public class MediaTileViewController: UICollectionViewController, MediaGalleryDa
|
|||
|
||||
// MARK: MediaGalleryDataSourceDelegate
|
||||
|
||||
func mediaGalleryDataSource(_ mediaGalleryDataSource: MediaGalleryDataSource, willDelete items: [MediaGalleryItem], initiatedBy: MediaGalleryDataSourceDelegate) {
|
||||
func mediaGalleryDataSource(_ mediaGalleryDataSource: MediaGalleryDataSource, willDelete items: [MediaGalleryItem], initiatedBy: AnyObject) {
|
||||
Logger.debug("")
|
||||
|
||||
guard let collectionView = self.collectionView else {
|
||||
|
|
|
@ -12,8 +12,17 @@ enum MessageMetadataViewMode: UInt {
|
|||
case focusOnMetadata
|
||||
}
|
||||
|
||||
@objc
|
||||
protocol MessageDetailViewDelegate: AnyObject {
|
||||
func detailViewMessageWasDeleted(_ messageDetailViewController: MessageDetailViewController)
|
||||
}
|
||||
|
||||
@objc
|
||||
class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDelegate, OWSMessageBubbleViewDelegate, ContactShareViewHelperDelegate {
|
||||
|
||||
@objc
|
||||
weak var delegate: MessageDetailViewDelegate?
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
let uiDatabaseConnection: YapDatabaseConnection
|
||||
|
@ -67,7 +76,7 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
|
|||
self.viewItem = viewItem
|
||||
self.message = message
|
||||
self.mode = mode
|
||||
self.uiDatabaseConnection = OWSPrimaryStorage.shared().newDatabaseConnection()
|
||||
self.uiDatabaseConnection = OWSPrimaryStorage.shared().uiDatabaseConnection
|
||||
self.conversationStyle = ConversationStyle(thread: thread)
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
@ -80,8 +89,13 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
|
|||
self.contactShareViewHelper = ContactShareViewHelper(contactsManager: contactsManager)
|
||||
contactShareViewHelper.delegate = self
|
||||
|
||||
self.uiDatabaseConnection.beginLongLivedReadTransaction()
|
||||
updateDBConnectionAndMessageToLatest()
|
||||
do {
|
||||
try updateDBConnectionAndMessageToLatest()
|
||||
} catch DetailViewError.messageWasDeleted {
|
||||
self.delegate?.detailViewMessageWasDeleted(self)
|
||||
} catch {
|
||||
owsFailDebug("unexpected error")
|
||||
}
|
||||
|
||||
self.conversationStyle.viewWidth = view.width()
|
||||
|
||||
|
@ -93,8 +107,8 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
|
|||
self.view.layoutIfNeeded()
|
||||
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(yapDatabaseModified),
|
||||
name: NSNotification.Name.YapDatabaseModified,
|
||||
selector: #selector(uiDatabaseDidUpdate),
|
||||
name: .OWSUIDatabaseConnectionDidUpdate,
|
||||
object: OWSPrimaryStorage.shared().dbNotificationObject)
|
||||
}
|
||||
|
||||
|
@ -524,19 +538,23 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
|
|||
|
||||
// MARK: - Actions
|
||||
|
||||
enum DetailViewError: Error {
|
||||
case messageWasDeleted
|
||||
}
|
||||
|
||||
// This method should be called after self.databaseConnection.beginLongLivedReadTransaction().
|
||||
private func updateDBConnectionAndMessageToLatest() {
|
||||
private func updateDBConnectionAndMessageToLatest() throws {
|
||||
|
||||
AssertIsOnMainThread()
|
||||
|
||||
self.uiDatabaseConnection.read { transaction in
|
||||
try self.uiDatabaseConnection.read { transaction in
|
||||
guard let uniqueId = self.message.uniqueId else {
|
||||
Logger.error("Message is missing uniqueId.")
|
||||
return
|
||||
}
|
||||
guard let newMessage = TSInteraction.fetch(uniqueId: uniqueId, transaction: transaction) as? TSMessage else {
|
||||
Logger.error("Couldn't reload message.")
|
||||
return
|
||||
Logger.error("Message was deleted")
|
||||
throw DetailViewError.messageWasDeleted
|
||||
}
|
||||
self.message = newMessage
|
||||
self.attachment = self.fetchAttachment(transaction: transaction)
|
||||
|
@ -544,20 +562,25 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
|
|||
}
|
||||
}
|
||||
|
||||
@objc internal func yapDatabaseModified(notification: NSNotification) {
|
||||
@objc internal func uiDatabaseDidUpdate(notification: NSNotification) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
guard !wasDeleted else {
|
||||
// Item was deleted. Don't bother re-rendering, it will fail and we'll soon be dismissed.
|
||||
// Item was deleted in the tile view gallery.
|
||||
// Don't bother re-rendering, it will fail and we'll soon be dismissed.
|
||||
return
|
||||
}
|
||||
|
||||
let notifications = self.uiDatabaseConnection.beginLongLivedReadTransaction()
|
||||
guard let notifications = notification.userInfo?[OWSUIDatabaseConnectionNotificationsKey] as? [Notification] else {
|
||||
owsFailDebug("notifications was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
guard let uniqueId = self.message.uniqueId else {
|
||||
Logger.error("Message is missing uniqueId.")
|
||||
return
|
||||
}
|
||||
|
||||
guard self.uiDatabaseConnection.hasChange(forKey: uniqueId,
|
||||
inCollection: TSInteraction.collection(),
|
||||
in: notifications) else {
|
||||
|
@ -565,7 +588,16 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
|
|||
return
|
||||
}
|
||||
|
||||
updateDBConnectionAndMessageToLatest()
|
||||
do {
|
||||
try updateDBConnectionAndMessageToLatest()
|
||||
} catch DetailViewError.messageWasDeleted {
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.detailViewMessageWasDeleted(self)
|
||||
}
|
||||
return
|
||||
} catch {
|
||||
owsFailDebug("unexpected error: \(error)")
|
||||
}
|
||||
updateContent()
|
||||
}
|
||||
|
||||
|
@ -616,14 +648,14 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
|
|||
// MARK: OWSMessageBubbleViewDelegate
|
||||
|
||||
func didTapImageViewItem(_ viewItem: ConversationViewItem, attachmentStream: TSAttachmentStream, imageView: UIView) {
|
||||
let mediaGallery = MediaGallery(thread: self.thread, uiDatabaseConnection: self.uiDatabaseConnection)
|
||||
let mediaGallery = MediaGallery(thread: self.thread)
|
||||
|
||||
mediaGallery.addDataSourceDelegate(self)
|
||||
mediaGallery.presentDetailView(fromViewController: self, mediaAttachment: attachmentStream, replacingView: imageView)
|
||||
}
|
||||
|
||||
func didTapVideoViewItem(_ viewItem: ConversationViewItem, attachmentStream: TSAttachmentStream, imageView: UIView) {
|
||||
let mediaGallery = MediaGallery(thread: self.thread, uiDatabaseConnection: self.uiDatabaseConnection)
|
||||
let mediaGallery = MediaGallery(thread: self.thread)
|
||||
|
||||
mediaGallery.addDataSourceDelegate(self)
|
||||
mediaGallery.presentDetailView(fromViewController: self, mediaAttachment: attachmentStream, replacingView: imageView)
|
||||
|
@ -732,7 +764,7 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
|
|||
|
||||
// MediaGalleryDataSourceDelegate
|
||||
|
||||
func mediaGalleryDataSource(_ mediaGalleryDataSource: MediaGalleryDataSource, willDelete items: [MediaGalleryItem], initiatedBy: MediaGalleryDataSourceDelegate) {
|
||||
func mediaGalleryDataSource(_ mediaGalleryDataSource: MediaGalleryDataSource, willDelete items: [MediaGalleryItem], initiatedBy: AnyObject) {
|
||||
Logger.info("")
|
||||
|
||||
guard (items.map({ $0.message }) == [self.message]) else {
|
||||
|
|
|
@ -1318,7 +1318,6 @@ const CGFloat kIconViewLength = 24;
|
|||
OWSLogDebug(@"");
|
||||
|
||||
MediaGallery *mediaGallery = [[MediaGallery alloc] initWithThread:self.thread
|
||||
uiDatabaseConnection:self.uiDatabaseConnection
|
||||
options:MediaGalleryOptionSliderEnabled];
|
||||
|
||||
self.mediaGallery = mediaGallery;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
@ -9,7 +9,10 @@ import Foundation
|
|||
///
|
||||
/// BenchAsync(title: "my benchmark") { completeBenchmark in
|
||||
/// foo {
|
||||
/// // consider benchmarking of "foo" complete
|
||||
/// completeBenchmark()
|
||||
///
|
||||
/// // call any completion handler foo might have
|
||||
/// fooCompletion()
|
||||
/// }
|
||||
/// }
|
||||
|
@ -30,6 +33,21 @@ public func Bench(title: String, block: () -> Void) {
|
|||
}
|
||||
}
|
||||
|
||||
public func Bench(title: String, block: () throws -> Void) throws {
|
||||
var thrownError: Error?
|
||||
BenchAsync(title: title) { finish in
|
||||
do {
|
||||
try block()
|
||||
} catch {
|
||||
thrownError = error
|
||||
}
|
||||
finish()
|
||||
}
|
||||
if let errorToRethrow = thrownError {
|
||||
throw errorToRethrow
|
||||
}
|
||||
}
|
||||
|
||||
/// When it's not convenient to retain the event completion handler, e.g. when the measured event
|
||||
/// crosses multiple classes, you can use the BenchEvent tools
|
||||
///
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
@ -7,7 +7,10 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
@class OWSStorage;
|
||||
@class TSAttachment;
|
||||
@class TSThread;
|
||||
@class YapDatabaseAutoViewTransaction;
|
||||
@class YapDatabaseConnection;
|
||||
@class YapDatabaseReadTransaction;
|
||||
@class YapDatabaseViewRowChange;
|
||||
|
||||
@interface OWSMediaGalleryFinder : NSObject
|
||||
|
||||
|
@ -19,8 +22,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
- (NSUInteger)mediaCountWithTransaction:(YapDatabaseReadTransaction *)transaction NS_SWIFT_NAME(mediaCount(transaction:));
|
||||
|
||||
// The ordinal position of an attachment within a thread's media gallery
|
||||
- (NSUInteger)mediaIndexForAttachment:(TSAttachment *)attachment
|
||||
transaction:(YapDatabaseReadTransaction *)transaction
|
||||
- (nullable NSNumber *)mediaIndexForAttachment:(TSAttachment *)attachment
|
||||
transaction:(YapDatabaseReadTransaction *)transaction
|
||||
NS_SWIFT_NAME(mediaIndex(attachment:transaction:));
|
||||
|
||||
- (nullable TSAttachment *)oldestMediaAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction
|
||||
|
@ -33,8 +36,14 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
block:(void (^)(TSAttachment *))attachmentBlock
|
||||
NS_SWIFT_NAME(enumerateMediaAttachments(range:transaction:block:));
|
||||
|
||||
- (BOOL)hasMediaChangesInNotifications:(NSArray<NSNotification *> *)notifications
|
||||
dbConnection:(YapDatabaseConnection *)dbConnection;
|
||||
|
||||
#pragma mark - Extension registration
|
||||
|
||||
@property (nonatomic, readonly) NSString *mediaGroup;
|
||||
- (YapDatabaseAutoViewTransaction *)galleryExtensionWithTransaction:(YapDatabaseReadTransaction *)transaction
|
||||
NS_SWIFT_NAME(galleryExtension(transaction:));
|
||||
+ (NSString *)databaseExtensionName;
|
||||
+ (void)asyncRegisterDatabaseExtensionsWithPrimaryStorage:(OWSStorage *)storage;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSMediaGalleryFinder.h"
|
||||
|
@ -43,7 +43,8 @@ static NSString *const OWSMediaGalleryFinderExtensionName = @"OWSMediaGalleryFin
|
|||
return [[self galleryExtensionWithTransaction:transaction] numberOfItemsInGroup:self.mediaGroup];
|
||||
}
|
||||
|
||||
- (NSUInteger)mediaIndexForAttachment:(TSAttachment *)attachment transaction:(YapDatabaseReadTransaction *)transaction
|
||||
- (nullable NSNumber *)mediaIndexForAttachment:(TSAttachment *)attachment
|
||||
transaction:(YapDatabaseReadTransaction *)transaction
|
||||
{
|
||||
NSString *groupId;
|
||||
NSUInteger index;
|
||||
|
@ -53,10 +54,13 @@ static NSString *const OWSMediaGalleryFinderExtensionName = @"OWSMediaGalleryFin
|
|||
forKey:attachment.uniqueId
|
||||
inCollection:[TSAttachment collection]];
|
||||
|
||||
OWSAssertDebug(wasFound);
|
||||
if (!wasFound) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
OWSAssertDebug([self.mediaGroup isEqual:groupId]);
|
||||
|
||||
return index;
|
||||
return @(index);
|
||||
}
|
||||
|
||||
- (nullable TSAttachment *)oldestMediaAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction
|
||||
|
@ -88,6 +92,15 @@ static NSString *const OWSMediaGalleryFinderExtensionName = @"OWSMediaGalleryFin
|
|||
}];
|
||||
}
|
||||
|
||||
- (BOOL)hasMediaChangesInNotifications:(NSArray<NSNotification *> *)notifications
|
||||
dbConnection:(YapDatabaseConnection *)dbConnection
|
||||
{
|
||||
YapDatabaseAutoViewConnection *extConnection = [dbConnection ext:OWSMediaGalleryFinderExtensionName];
|
||||
OWSAssert(extConnection);
|
||||
|
||||
return [extConnection hasChangesForGroup:self.mediaGroup inNotifications:notifications];
|
||||
}
|
||||
|
||||
#pragma mark - Util
|
||||
|
||||
- (YapDatabaseAutoViewTransaction *)galleryExtensionWithTransaction:(YapDatabaseReadTransaction *)transaction
|
||||
|
|
|
@ -17,4 +17,36 @@ public extension YapDatabaseConnection {
|
|||
self.asyncReadWrite(block, completionBlock: resolver.fulfill)
|
||||
}
|
||||
}
|
||||
|
||||
func read(_ block: @escaping (YapDatabaseReadTransaction) throws -> Void) throws {
|
||||
var errorToRaise: Error? = nil
|
||||
|
||||
read { transaction in
|
||||
do {
|
||||
try block(transaction)
|
||||
} catch {
|
||||
errorToRaise = error
|
||||
}
|
||||
}
|
||||
|
||||
if let errorToRaise = errorToRaise {
|
||||
throw errorToRaise
|
||||
}
|
||||
}
|
||||
|
||||
func readWrite(_ block: @escaping (YapDatabaseReadWriteTransaction) throws -> Void) throws {
|
||||
var errorToRaise: Error? = nil
|
||||
|
||||
readWrite { transaction in
|
||||
do {
|
||||
try block(transaction)
|
||||
} catch {
|
||||
errorToRaise = error
|
||||
}
|
||||
}
|
||||
|
||||
if let errorToRaise = errorToRaise {
|
||||
throw errorToRaise
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue