|
@@ -4,8 +4,9 @@ import SDWebImage
|
|
|
|
|
|
class GalleryViewController: UIViewController {
|
|
|
|
|
|
+ private let dcContext: DcContext
|
|
|
// MARK: - data
|
|
|
- private let mediaMessageIds: [Int]
|
|
|
+ private var mediaMessageIds: [Int]
|
|
|
private var items: [Int: GalleryItem] = [:]
|
|
|
|
|
|
// MARK: - subview specs
|
|
@@ -46,7 +47,8 @@ class GalleryViewController: UIViewController {
|
|
|
return label
|
|
|
}()
|
|
|
|
|
|
- init(mediaMessageIds: [Int]) {
|
|
|
+ init(context: DcContext, mediaMessageIds: [Int]) {
|
|
|
+ self.dcContext = context
|
|
|
self.mediaMessageIds = mediaMessageIds
|
|
|
super.init(nibName: nil, bundle: nil)
|
|
|
}
|
|
@@ -67,6 +69,7 @@ class GalleryViewController: UIViewController {
|
|
|
|
|
|
override func viewWillAppear(_ animated: Bool) {
|
|
|
grid.reloadData()
|
|
|
+ setupContextMenuIfNeeded()
|
|
|
}
|
|
|
|
|
|
override func viewWillLayoutSubviews() {
|
|
@@ -96,6 +99,13 @@ class GalleryViewController: UIViewController {
|
|
|
emptyStateView.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor).isActive = true
|
|
|
}
|
|
|
|
|
|
+ private func setupContextMenuIfNeeded() {
|
|
|
+ UIMenuController.shared.menuItems = [
|
|
|
+ UIMenuItem(title: String.localized("delete"), action: #selector(GalleryCell.itemDelete(_:))),
|
|
|
+ ]
|
|
|
+ UIMenuController.shared.update()
|
|
|
+ }
|
|
|
+
|
|
|
// MARK: - updates
|
|
|
private func updateFloatingTimeLabel() {
|
|
|
if let indexPath = grid.indexPathsForVisibleItems.min() {
|
|
@@ -104,11 +114,17 @@ class GalleryViewController: UIViewController {
|
|
|
timeLabel.update(date: msg.sentDate)
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ private func deleteItem(at indexPath: IndexPath) {
|
|
|
+ let msgId = mediaMessageIds.remove(at: indexPath.row)
|
|
|
+ self.dcContext.deleteMessage(msgId: msgId)
|
|
|
+ self.grid.deleteItems(at: [indexPath])
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
extension GalleryViewController: UICollectionViewDataSourcePrefetching {
|
|
|
func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
|
|
|
- indexPaths.forEach { if items[$0.row] == nil {
|
|
|
+ indexPaths.forEach { if items[$0.row] == nil {
|
|
|
let item = GalleryItem(msgId: mediaMessageIds[$0.row])
|
|
|
items[$0.row] = item
|
|
|
}}
|
|
@@ -128,8 +144,8 @@ extension GalleryViewController: UICollectionViewDataSource, UICollectionViewDel
|
|
|
|
|
|
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
|
|
guard let galleryCell = collectionView.dequeueReusableCell(
|
|
|
- withReuseIdentifier: GalleryCell.reuseIdentifier,
|
|
|
- for: indexPath) as? GalleryCell else {
|
|
|
+ withReuseIdentifier: GalleryCell.reuseIdentifier,
|
|
|
+ for: indexPath) as? GalleryCell else {
|
|
|
return UICollectionViewCell()
|
|
|
}
|
|
|
|
|
@@ -143,6 +159,7 @@ extension GalleryViewController: UICollectionViewDataSource, UICollectionViewDel
|
|
|
item = galleryItem
|
|
|
}
|
|
|
galleryCell.update(item: item)
|
|
|
+ UIMenuController.shared.setMenuVisible(false, animated: true)
|
|
|
return galleryCell
|
|
|
}
|
|
|
|
|
@@ -150,6 +167,7 @@ extension GalleryViewController: UICollectionViewDataSource, UICollectionViewDel
|
|
|
let msgId = mediaMessageIds[indexPath.row]
|
|
|
showPreview(msgId: msgId)
|
|
|
collectionView.deselectItem(at: indexPath, animated: true)
|
|
|
+ UIMenuController.shared.setMenuVisible(false, animated: true)
|
|
|
}
|
|
|
|
|
|
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
|
@@ -164,6 +182,62 @@ extension GalleryViewController: UICollectionViewDataSource, UICollectionViewDel
|
|
|
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
|
|
timeLabel.hide(animated: true)
|
|
|
}
|
|
|
+
|
|
|
+ func collectionView(_ collectionView: UICollectionView, shouldShowMenuForItemAt indexPath: IndexPath) -> Bool {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ // MARK: - context menu
|
|
|
+ // context menu for iOS 11, 12
|
|
|
+ func collectionView(_ collectionView: UICollectionView, canPerformAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
|
|
|
+ return action == #selector(GalleryCell.itemDelete(_:))
|
|
|
+ }
|
|
|
+
|
|
|
+ func collectionView(_ collectionView: UICollectionView, performAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) {
|
|
|
+
|
|
|
+ switch action {
|
|
|
+ case #selector(GalleryCell.itemDelete(_:)):
|
|
|
+ deleteItem(at: indexPath)
|
|
|
+ default:
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // context menu for iOS 13+
|
|
|
+ @available(iOS 13, *)
|
|
|
+ func collectionView(_ collectionView: UICollectionView,
|
|
|
+ contextMenuConfigurationForItemAt indexPath: IndexPath,
|
|
|
+ point: CGPoint) -> UIContextMenuConfiguration? {
|
|
|
+ guard let galleryCell = collectionView.cellForItem(at: indexPath) as? GalleryCell, let item = galleryCell.item else {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ return UIContextMenuConfiguration(
|
|
|
+ identifier: nil,
|
|
|
+ previewProvider: {
|
|
|
+ return XLPreviewViewController(imageUrl: item.fileUrl)
|
|
|
+ },
|
|
|
+ actionProvider: { [weak self] _ in
|
|
|
+ return self?.makeContextMenu(indexPath: indexPath)
|
|
|
+ }
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ @available(iOS 13, *)
|
|
|
+ private func makeContextMenu(indexPath: IndexPath) -> UIMenu {
|
|
|
+ let deleteAction = UIAction(
|
|
|
+ title: String.localized("delete"),
|
|
|
+ image: UIImage(systemName: "trash")) { _ in
|
|
|
+ self.deleteItem(at: indexPath)
|
|
|
+ }
|
|
|
+
|
|
|
+ return UIMenu(
|
|
|
+ title: "",
|
|
|
+ image: nil,
|
|
|
+ identifier: nil,
|
|
|
+ children: [deleteAction]
|
|
|
+ )
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// MARK: - grid layout + updates
|
|
@@ -210,11 +284,53 @@ extension GalleryViewController {
|
|
|
guard let index = mediaMessageIds.index(of: msgId) else {
|
|
|
return
|
|
|
}
|
|
|
+
|
|
|
let previewController = PreviewController(type: .multi(mediaMessageIds, index))
|
|
|
present(previewController, animated: true, completion: nil)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+private class XLPreviewViewController: UIViewController {
|
|
|
+
|
|
|
+ private lazy var imageView: UIImageView = {
|
|
|
+ let imageView = UIImageView()
|
|
|
+ imageView.clipsToBounds = true
|
|
|
+ imageView.contentMode = .scaleAspectFill
|
|
|
+ imageView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ return imageView
|
|
|
+ }()
|
|
|
+
|
|
|
+ init(imageUrl: URL?) {
|
|
|
+ super.init(nibName: nil, bundle: nil)
|
|
|
+ if let url = imageUrl {
|
|
|
+ imageView.image = UIImage(named: url.relativePath)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ required init?(coder: NSCoder) {
|
|
|
+ fatalError("init(coder:) has not been implemented")
|
|
|
+ }
|
|
|
+
|
|
|
+ override func viewDidLoad() {
|
|
|
+ super.viewDidLoad()
|
|
|
+
|
|
|
+ view.addSubview(imageView)
|
|
|
+
|
|
|
+ NSLayoutConstraint.activate([
|
|
|
+ imageView.leftAnchor.constraint(equalTo: view.leftAnchor),
|
|
|
+ imageView.rightAnchor.constraint(equalTo: view.rightAnchor),
|
|
|
+ imageView.topAnchor.constraint(equalTo: view.topAnchor),
|
|
|
+ imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
|
|
+ ])
|
|
|
+
|
|
|
+ let width = view.bounds.width
|
|
|
+ if let image = imageView.image {
|
|
|
+ let height = image.size.height * (width / image.size.width)
|
|
|
+ preferredContentSize = CGSize(width: width, height: height)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
class GalleryItem {
|
|
|
|
|
|
var onImageLoaded: ((UIImage?) -> Void)?
|