diff --git a/Signal/Images.xcassets/album_add_more.imageset/Contents.json b/Signal/Images.xcassets/album_add_more.imageset/Contents.json new file mode 100644 index 000000000..b4dcc1357 --- /dev/null +++ b/Signal/Images.xcassets/album_add_more.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "album_add_more@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "album_add_more@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "album_add_more@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/album_add_more.imageset/album_add_more@1x.png b/Signal/Images.xcassets/album_add_more.imageset/album_add_more@1x.png new file mode 100644 index 000000000..bc89e5a4f Binary files /dev/null and b/Signal/Images.xcassets/album_add_more.imageset/album_add_more@1x.png differ diff --git a/Signal/Images.xcassets/album_add_more.imageset/album_add_more@2x.png b/Signal/Images.xcassets/album_add_more.imageset/album_add_more@2x.png new file mode 100644 index 000000000..b5102c674 Binary files /dev/null and b/Signal/Images.xcassets/album_add_more.imageset/album_add_more@2x.png differ diff --git a/Signal/Images.xcassets/album_add_more.imageset/album_add_more@3x.png b/Signal/Images.xcassets/album_add_more.imageset/album_add_more@3x.png new file mode 100644 index 000000000..87e6bf6f2 Binary files /dev/null and b/Signal/Images.xcassets/album_add_more.imageset/album_add_more@3x.png differ diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 68df293ab..70e0248c8 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -2676,7 +2676,7 @@ typedef enum : NSUInteger { - (void)imagePicker:(OWSImagePickerGridController *)imagePicker didPickImageAttachments:(NSArray *)attachments { - [self tryToSendAttachmentsIfApproved:attachments]; + [self tryToSendAttachmentsIfApproved:attachments skipApprovalDialog:YES]; } /* diff --git a/Signal/src/ViewControllers/PhotoLibrary/ImagePickerController.swift b/Signal/src/ViewControllers/PhotoLibrary/ImagePickerController.swift index 1ed56ff00..f48ea7c4a 100644 --- a/Signal/src/ViewControllers/PhotoLibrary/ImagePickerController.swift +++ b/Signal/src/ViewControllers/PhotoLibrary/ImagePickerController.swift @@ -12,7 +12,7 @@ protocol ImagePickerControllerDelegate { } @objc(OWSImagePickerGridController) -class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegate, PhotoCollectionPickerDelegate { +class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegate, PhotoCollectionPickerDelegate, AttachmentApprovalViewControllerDelegate { @objc weak var delegate: ImagePickerControllerDelegate? @@ -26,6 +26,11 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat private let titleLabel = UILabel() + private var selectedIds = Set() + + // This variable should only be accessed on the main thread. + private var assetIdToCommentMap = [String: String]() + init() { collectionViewFlowLayout = type(of: self).buildLayout() photoCollection = library.defaultPhotoCollection() @@ -38,7 +43,7 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat fatalError("init(coder:) has not been implemented") } - // MARK: View Lifecycle + // MARK: - View Lifecycle override func viewDidLoad() { super.viewDidLoad() @@ -52,17 +57,27 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat collectionView.register(PhotoGridViewCell.self, forCellWithReuseIdentifier: PhotoGridViewCell.reuseIdentifier) - navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, - target: self, - action: #selector(didPressCancel)) + view.backgroundColor = .ows_gray95 + + if let navBar = self.navigationController?.navigationBar as? OWSNavigationBar { + navBar.makeClear() + } else { + owsFailDebug("Invalid nav bar.") + } + + let cancelButton = UIBarButtonItem(barButtonSystemItem: .stop, + target: self, + action: #selector(didPressCancel)) + cancelButton.tintColor = .ows_gray05 + navigationItem.leftBarButtonItem = cancelButton if #available(iOS 11, *) { titleLabel.text = photoCollection.localizedTitle() - titleLabel.textColor = Theme.primaryColor + titleLabel.textColor = .ows_gray05 titleLabel.font = UIFont.ows_dynamicTypeBody.ows_mediumWeight() let titleIconView = UIImageView() - titleIconView.tintColor = Theme.primaryColor + titleIconView.tintColor = .ows_gray05 titleIconView.image = UIImage(named: "navbar_disclosure_down")?.withRenderingMode(.alwaysTemplate) let titleView = UIStackView(arrangedSubviews: [titleLabel, titleIconView]) @@ -81,7 +96,7 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat updateSelectButton() } - collectionView.backgroundColor = Theme.backgroundColor + collectionView.backgroundColor = .ows_gray95 } override func viewWillLayoutSubviews() { @@ -96,16 +111,38 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat let scale = UIScreen.main.scale let cellSize = collectionViewFlowLayout.itemSize photoMediaSize.thumbnailSize = CGSize(width: cellSize.width * scale, height: cellSize.height * scale) + + reloadDataAndRestoreSelection() + } + + private func reloadDataAndRestoreSelection() { + guard let collectionView = collectionView else { + owsFailDebug("Missing collectionView.") + return + } + + collectionView.reloadData() + collectionView.layoutIfNeeded() + + let count = photoCollectionContents.count + for index in 0.. UICollectionViewFlowLayout { @@ -146,7 +183,7 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat } } - // MARK: Batch Selection + // MARK: - Batch Selection lazy var doneButton: UIBarButtonItem = { return UIBarButtonItem(barButtonSystemItem: .done, @@ -184,12 +221,39 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat } let assets: [PHAsset] = indexPaths.compactMap { return photoCollectionContents.asset(at: $0.row) } - let promises = assets.map { return photoCollectionContents.outgoingAttachment(for: $0) } - when(fulfilled: promises).map { attachments in - self.dismiss(animated: true) { - self.delegate?.imagePicker(self, didPickImageAttachments: attachments) + complete(withAssets: assets) + } + + func complete(withAssets assets: [PHAsset]) { + let attachmentPromises: [Promise] = assets.map({ + return photoCollectionContents.outgoingAttachment(for: $0) + }) + when(fulfilled: attachmentPromises) + .map { attachments in + self.didComplete(withAttachments: attachments) + }.retainUntilComplete() + } + + private func didComplete(withAttachments attachments: [SignalAttachment]) { + AssertIsOnMainThread() + + // If we re-enter image picking, do so in batch mode. + isInBatchSelectMode = true + + for attachment in attachments { + guard let assetId = attachment.assetId else { + owsFailDebug("Attachment is missing asset id.") + continue } - }.retainUntilComplete() + // Link the attachment with its asset to ensure caption continuity. + attachment.assetId = assetId + // Restore any existing caption for this attachment. + attachment.captionText = assetIdToCommentMap[assetId] + } + + let vc = AttachmentApprovalViewController(mode: .sharedNavigation, attachments: attachments) + vc.approvalDelegate = self + navigationController?.pushViewController(vc, animated: true) } func updateDoneButton() { @@ -206,7 +270,9 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat } func updateSelectButton() { - navigationItem.rightBarButtonItem = isInBatchSelectMode ? doneButton : selectButton + let button = isInBatchSelectMode ? doneButton : selectButton + button.tintColor = .ows_gray05 + navigationItem.rightBarButtonItem = button } @objc @@ -234,13 +300,17 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat collectionView.indexPathsForSelectedItems?.forEach { collectionView.deselectItem(at: $0, animated: false)} } - // MARK: PhotoLibraryDelegate + // MARK: - PhotoLibraryDelegate func photoLibraryDidChange(_ photoLibrary: PhotoLibrary) { - collectionView?.reloadData() + // We only want to let users select assets + // from a single collection. + selectedIds.removeAll() + + reloadDataAndRestoreSelection() } - // MARK: PhotoCollectionPickerDelegate + // MARK: - PhotoCollectionPickerDelegate func photoCollectionPicker(_ photoCollectionPicker: PhotoCollectionPickerController, didPickCollection collection: PhotoCollection) { photoCollection = collection @@ -252,7 +322,7 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat navigationItem.title = photoCollection.localizedTitle() } - collectionView?.reloadData() + reloadDataAndRestoreSelection() } // MARK: - Event Handlers @@ -264,30 +334,32 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat let view = PhotoCollectionPickerController(library: library, previousPhotoCollection: photoCollection, collectionDelegate: self) - let nav = UINavigationController(rootViewController: view) + let nav = OWSNavigationController(rootViewController: view) self.present(nav, animated: true, completion: nil) } - // MARK: UICollectionView + // MARK: - UICollectionView override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + + let asset = photoCollectionContents.asset(at: indexPath.item) + let assetId = asset.localIdentifier + selectedIds.insert(assetId) + if isInBatchSelectMode { updateDoneButton() } else { - let asset = photoCollectionContents.asset(at: indexPath.row) - firstly { - photoCollectionContents.outgoingAttachment(for: asset) - }.map { attachment in - self.dismiss(animated: true) { - self.delegate?.imagePicker(self, didPickImageAttachments: [attachment]) - } - }.retainUntilComplete() + complete(withAssets: [asset]) } } public override func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { Logger.debug("") + let asset = photoCollectionContents.asset(at: indexPath.item) + let assetId = asset.localIdentifier + selectedIds.remove(assetId) + if isInBatchSelectMode { updateDoneButton() } @@ -301,10 +373,44 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PhotoGridViewCell.reuseIdentifier, for: indexPath) as? PhotoGridViewCell else { owsFail("cell was unexpectedly nil") } - + cell.loadingColor = UIColor(white: 0.2, alpha: 1) let assetItem = photoCollectionContents.assetItem(at: indexPath.item, photoMediaSize: photoMediaSize) cell.configure(item: assetItem) + + let assetId = assetItem.asset.localIdentifier + let isSelected = selectedIds.contains(assetId) + cell.isSelected = isSelected + return cell } + // MARK: - AttachmentApprovalViewControllerDelegate + + func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didApproveAttachments attachments: [SignalAttachment]) { + self.dismiss(animated: true) { + self.delegate?.imagePicker(self, didPickImageAttachments: attachments) + } + } + + func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didCancelAttachments attachments: [SignalAttachment]) { + navigationController?.popToViewController(self, animated: true) + } + + func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, addMoreToAttachments attachments: [SignalAttachment]) { + navigationController?.popToViewController(self, animated: true) + } + + func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, changedCaptionOfAttachment attachment: SignalAttachment) { + AssertIsOnMainThread() + + guard let assetId = attachment.assetId else { + owsFailDebug("Attachment missing source id.") + return + } + guard let captionText = attachment.captionText, captionText.count > 0 else { + assetIdToCommentMap.removeValue(forKey: assetId) + return + } + assetIdToCommentMap[assetId] = captionText + } } diff --git a/Signal/src/ViewControllers/PhotoLibrary/PhotoCollectionPickerController.swift b/Signal/src/ViewControllers/PhotoLibrary/PhotoCollectionPickerController.swift index 4c5c4a981..6f2748122 100644 --- a/Signal/src/ViewControllers/PhotoLibrary/PhotoCollectionPickerController.swift +++ b/Signal/src/ViewControllers/PhotoLibrary/PhotoCollectionPickerController.swift @@ -37,14 +37,22 @@ class PhotoCollectionPickerController: OWSTableViewController, PhotoLibraryDeleg override func viewDidLoad() { super.viewDidLoad() + view.backgroundColor = .ows_gray95 + + if let navBar = self.navigationController?.navigationBar as? OWSNavigationBar { + navBar.makeClear() + } else { + owsFailDebug("Invalid nav bar.") + } + if #available(iOS 11, *) { let titleLabel = UILabel() titleLabel.text = previousPhotoCollection.localizedTitle() - titleLabel.textColor = Theme.primaryColor + titleLabel.textColor = .ows_gray05 titleLabel.font = UIFont.ows_dynamicTypeBody.ows_mediumWeight() let titleIconView = UIImageView() - titleIconView.tintColor = Theme.primaryColor + titleIconView.tintColor = .ows_gray05 titleIconView.image = UIImage(named: "navbar_disclosure_up")?.withRenderingMode(.alwaysTemplate) let titleView = UIStackView(arrangedSubviews: [titleLabel, titleIconView]) @@ -60,9 +68,11 @@ class PhotoCollectionPickerController: OWSTableViewController, PhotoLibraryDeleg library.add(delegate: self) - navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, - target: self, - action: #selector(didPressCancel)) + let cancelButton = UIBarButtonItem(barButtonSystemItem: .stop, + target: self, + action: #selector(didPressCancel)) + cancelButton.tintColor = .ows_gray05 + navigationItem.leftBarButtonItem = cancelButton updateContents() } @@ -75,6 +85,10 @@ class PhotoCollectionPickerController: OWSTableViewController, PhotoLibraryDeleg section.add(OWSTableItem.init(customCellBlock: { () -> UITableViewCell in let cell = OWSTableItem.newCell() + cell.backgroundColor = .ows_gray95 + cell.contentView.backgroundColor = .ows_gray95 + cell.selectedBackgroundView?.backgroundColor = UIColor(white: 0.2, alpha: 1) + let imageView = UIImageView() let kImageSize = 50 imageView.autoSetDimensions(to: CGSize(width: kImageSize, height: kImageSize)) @@ -97,7 +111,7 @@ class PhotoCollectionPickerController: OWSTableViewController, PhotoLibraryDeleg let titleLabel = UILabel() titleLabel.text = collection.localizedTitle() titleLabel.font = UIFont.ows_dynamicTypeBody - titleLabel.textColor = Theme.primaryColor + titleLabel.textColor = .ows_gray05 let stackView = UIStackView(arrangedSubviews: [imageView, titleLabel]) stackView.axis = .horizontal @@ -120,6 +134,12 @@ class PhotoCollectionPickerController: OWSTableViewController, PhotoLibraryDeleg self.contents = contents } + @objc + public override func applyTheme() { + view.backgroundColor = .ows_gray95 + tableView.backgroundColor = .ows_gray95 + } + // MARK: Actions @objc diff --git a/Signal/src/ViewControllers/PhotoLibrary/PhotoLibrary.swift b/Signal/src/ViewControllers/PhotoLibrary/PhotoLibrary.swift index c9b62fc3b..bbcbc45dc 100644 --- a/Signal/src/ViewControllers/PhotoLibrary/PhotoLibrary.swift +++ b/Signal/src/ViewControllers/PhotoLibrary/PhotoLibrary.swift @@ -150,11 +150,15 @@ class PhotoCollectionContents { switch asset.mediaType { case .image: return requestImageDataSource(for: asset).map { (dataSource: DataSource, dataUTI: String) in - return SignalAttachment.attachment(dataSource: dataSource, dataUTI: dataUTI, imageQuality: .medium) + let attachment = SignalAttachment.attachment(dataSource: dataSource, dataUTI: dataUTI, imageQuality: .medium) + attachment.assetId = asset.localIdentifier + return attachment } case .video: return requestVideoDataSource(for: asset).map { (dataSource: DataSource, dataUTI: String) in - return SignalAttachment.attachment(dataSource: dataSource, dataUTI: dataUTI) + let attachment = SignalAttachment.attachment(dataSource: dataSource, dataUTI: dataUTI) + attachment.assetId = asset.localIdentifier + return attachment } default: return Promise(error: PhotoLibraryError.unsupportedMediaType) diff --git a/Signal/src/views/PhotoGridViewCell.swift b/Signal/src/views/PhotoGridViewCell.swift index 2114a94dc..fdc486914 100644 --- a/Signal/src/views/PhotoGridViewCell.swift +++ b/Signal/src/views/PhotoGridViewCell.swift @@ -29,6 +29,8 @@ public class PhotoGridViewCell: UICollectionViewCell { private static let animatedBadgeImage = #imageLiteral(resourceName: "ic_gallery_badge_gif") private static let selectedBadgeImage = #imageLiteral(resourceName: "selected_blue_circle") + public var loadingColor = Theme.offBackgroundColor + override public var isSelected: Bool { didSet { self.selectedBadgeView.isHidden = !self.isSelected @@ -99,7 +101,7 @@ public class PhotoGridViewCell: UICollectionViewCell { get { return imageView.image } set { imageView.image = newValue - imageView.backgroundColor = newValue == nil ? Theme.offBackgroundColor : .clear + imageView.backgroundColor = newValue == nil ? loadingColor : .clear } } diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index d41c5e00d..b748ebeb8 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -11,6 +11,8 @@ import PromiseKit public protocol AttachmentApprovalViewControllerDelegate: class { func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didApproveAttachments attachments: [SignalAttachment]) func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didCancelAttachments attachments: [SignalAttachment]) + @objc optional func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, addMoreToAttachments attachments: [SignalAttachment]) + @objc optional func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, changedCaptionOfAttachment attachment: SignalAttachment) } class AttachmentItemCollection { @@ -91,16 +93,24 @@ class SignalAttachmentItem: Hashable { } } +@objc +public enum AttachmentApprovalViewControllerMode: UInt { + case modal + case sharedNavigation +} + @objc public class AttachmentApprovalViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate, CaptioningToolbarDelegate { - // MARK: Properties + // MARK: - Properties - weak var approvalDelegate: AttachmentApprovalViewControllerDelegate? + private let mode: AttachmentApprovalViewControllerMode + + public weak var approvalDelegate: AttachmentApprovalViewControllerDelegate? private(set) var captioningToolbar: CaptioningToolbar! - // MARK: Initializers + // MARK: - Initializers @available(*, unavailable, message:"use attachment: constructor instead.") required public init?(coder aDecoder: NSCoder) { @@ -110,8 +120,10 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC let kSpacingBetweenItems: CGFloat = 20 @objc - required public init(attachments: [SignalAttachment]) { + required public init(mode: AttachmentApprovalViewControllerMode, + attachments: [SignalAttachment]) { assert(attachments.count > 0) + self.mode = mode let attachmentItems = attachments.map { SignalAttachmentItem(attachment: $0 )} self.attachmentItemCollection = AttachmentItemCollection(attachmentItems: attachmentItems) super.init(transitionStyle: .scroll, @@ -123,7 +135,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC @objc public class func wrappedInNavController(attachments: [SignalAttachment], approvalDelegate: AttachmentApprovalViewControllerDelegate) -> OWSNavigationController { - let vc = AttachmentApprovalViewController(attachments: attachments) + let vc = AttachmentApprovalViewController(mode: .modal, attachments: attachments) vc.approvalDelegate = approvalDelegate let navController = OWSNavigationController(rootViewController: vc) @@ -136,7 +148,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC return navController } - // MARK: View Lifecycle + // MARK: - View Lifecycle let galleryRailView = GalleryRailView() let railContainerView = UIView() @@ -171,7 +183,8 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC // Bottom Toolbar - let captioningToolbar = CaptioningToolbar() + let isAddMoreVisible = mode == .sharedNavigation + let captioningToolbar = CaptioningToolbar(isAddMoreVisible: isAddMoreVisible) captioningToolbar.captioningToolbarDelegate = self self.captioningToolbar = captioningToolbar @@ -179,9 +192,12 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC self.navigationItem.title = nil - let cancelButton = UIBarButtonItem(barButtonSystemItem: .stop, target: self, action: #selector(cancelPressed)) - cancelButton.tintColor = .white - self.navigationItem.leftBarButtonItem = cancelButton + if mode != .sharedNavigation { + let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, + target: self, action: #selector(cancelPressed)) + cancelButton.tintColor = .white + self.navigationItem.leftBarButtonItem = cancelButton + } guard let firstItem = attachmentItems.first else { owsFailDebug("firstItem was unexpectedly nil") @@ -189,6 +205,8 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC } self.setCurrentItem(firstItem, direction: .forward, animated: false) + + captioningToolbar.captionText = currentViewController.attachment.captionText } override public func viewWillAppear(_ animated: Bool) { @@ -263,8 +281,8 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC } let button = OWSButton { [weak self] in - guard let self = self else { return } - self.remove(attachmentItem: attachmentItem) + guard let strongSelf = self else { return } + strongSelf.remove(attachmentItem: attachmentItem) } button.setImage(#imageLiteral(resourceName: "ic_small_x"), for: .normal) @@ -300,21 +318,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC pagerScrollView.isScrollEnabled = attachmentItems.count > 1 } - private func makeClearToolbar() -> UIToolbar { - let toolbar = UIToolbar() - - toolbar.backgroundColor = UIColor.clear - - // Making a toolbar transparent requires setting an empty uiimage - toolbar.setBackgroundImage(UIImage(), forToolbarPosition: .any, barMetrics: .default) - - // hide 1px top-border - toolbar.clipsToBounds = true - - return toolbar - } - - // MARK: UIPageViewControllerDelegate + // MARK: - UIPageViewControllerDelegate public func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) { Logger.debug("") @@ -356,7 +360,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC } } - // MARK: UIPageViewControllerDataSource + // MARK: - UIPageViewControllerDataSource public func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { guard let currentViewController = viewController as? AttachmentPrepViewController else { @@ -493,7 +497,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC self.approvalDelegate?.attachmentApproval(self, didCancelAttachments: attachments) } - // MARK: CaptioningToolbarDelegate + // MARK: - CaptioningToolbarDelegate var currentPageController: AttachmentPrepViewController { return viewControllers!.first as! AttachmentPrepViewController @@ -520,6 +524,12 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC func captioningToolbar(_ captioningToolbar: CaptioningToolbar, textViewDidChange textView: UITextView) { currentItem.attachment.captionText = textView.text + + self.approvalDelegate?.attachmentApproval?(self, changedCaptionOfAttachment: currentItem.attachment) + } + + func captioningToolbarDidAddMore(_ captioningToolbar: CaptioningToolbar) { + self.approvalDelegate?.attachmentApproval?(self, addMoreToAttachments: attachments) } } @@ -558,7 +568,7 @@ extension AttachmentApprovalViewController: GalleryRailViewDelegate { return } - let direction: NavigationDirection = currentIndex < targetIndex ? .forward : .reverse + let direction: UIPageViewControllerNavigationDirection = currentIndex < targetIndex ? .forward : .reverse self.setCurrentItem(targetItem, direction: direction, animated: true) } @@ -573,7 +583,7 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD case fullsize, compact } - // MARK: Properties + // MARK: - Properties let attachmentItem: SignalAttachmentItem var attachment: SignalAttachment { @@ -587,7 +597,7 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD private(set) var contentContainer: UIView! private(set) var playVideoButton: UIView? - // MARK: Initializers + // MARK: - Initializers init(attachmentItem: SignalAttachmentItem) { self.attachmentItem = attachmentItem @@ -599,7 +609,7 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD fatalError("init(coder:) has not been implemented") } - // MARK: View Lifecycle + // MARK: - View Lifecycle override public func loadView() { self.view = UIView() @@ -713,7 +723,7 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD ensureAttachmentViewScale(animated: false) } - // MARK: + // MARK: - @objc public func didTapPlayerView(_ gestureRecognizer: UIGestureRecognizer) { assert(self.videoPlayer != nil) @@ -727,7 +737,7 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD self.playVideo() } - // MARK: Video + // MARK: - Video private func playVideo() { Logger.info("") @@ -804,7 +814,7 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD } } - // MARK: Helpers + // MARK: - Helpers var isZoomable: Bool { return attachment.isImage || attachment.isVideo @@ -939,11 +949,13 @@ protocol CaptioningToolbarDelegate: class { func captioningToolbarDidBeginEditing(_ captioningToolbar: CaptioningToolbar) func captioningToolbarDidEndEditing(_ captioningToolbar: CaptioningToolbar) func captioningToolbar(_ captioningToolbar: CaptioningToolbar, textViewDidChange: UITextView) + func captioningToolbarDidAddMore(_ captioningToolbar: CaptioningToolbar) } class CaptioningToolbar: UIView, UITextViewDelegate { weak var captioningToolbarDelegate: CaptioningToolbarDelegate? + private let addMoreButton: UIButton private let sendButton: UIButton private let textView: UITextView @@ -984,9 +996,10 @@ class CaptioningToolbar: UIView, UITextViewDelegate { } } - // MARK: Initializers + // MARK: - Initializers - init() { + init(isAddMoreVisible: Bool) { + self.addMoreButton = UIButton(type: .custom) self.sendButton = UIButton(type: .system) self.textView = MessageTextView() self.textViewHeight = kMinTextViewHeight @@ -1012,6 +1025,9 @@ class CaptioningToolbar: UIView, UITextViewDelegate { textView.textContainerInset = UIEdgeInsets(top: 7, left: 7, bottom: 7, right: 7) textView.scrollIndicatorInsets = UIEdgeInsets(top: 5, left: 0, bottom: 5, right: 3) + addMoreButton.setImage(UIImage(named: "album_add_more"), for: .normal) + addMoreButton.addTarget(self, action: #selector(didTapAddMore), for: .touchUpInside) + let sendTitle = NSLocalizedString("ATTACHMENT_APPROVAL_SEND_BUTTON", comment: "Label for 'send' button in the 'attachment approval' dialog.") sendButton.setTitle(sendTitle, for: .normal) sendButton.addTarget(self, action: #selector(didTapSend), for: .touchUpInside) @@ -1039,6 +1055,9 @@ class CaptioningToolbar: UIView, UITextViewDelegate { contentView.addSubview(sendButton) contentView.addSubview(textView) contentView.addSubview(lengthLimitLabel) + if isAddMoreVisible { + contentView.addSubview(addMoreButton) + } addSubview(contentView) contentView.autoPinEdgesToSuperviewEdges() @@ -1060,9 +1079,17 @@ class CaptioningToolbar: UIView, UITextViewDelegate { // So it doesn't work as expected with RTL layouts when we explicitly want something // to be on the right side for both RTL and LTR layouts, like with the send button. // I believe this is a bug in PureLayout. Filed here: https://github.com/PureLayout/PureLayout/issues/209 - textView.autoPinEdge(toSuperviewMargin: .left) textView.autoPinEdge(toSuperviewMargin: .top) textView.autoPinEdge(toSuperviewMargin: .bottom) + if isAddMoreVisible { + addMoreButton.autoPinEdge(toSuperviewMargin: .left) + textView.autoPinEdge(.left, to: .right, of: addMoreButton, withOffset: kToolbarMargin) + addMoreButton.autoAlignAxis(.horizontal, toSameAxisOf: sendButton) + addMoreButton.setContentHuggingHigh() + addMoreButton.setCompressionResistanceHigh() + } else { + textView.autoPinEdge(toSuperviewMargin: .left) + } sendButton.autoPinEdge(.left, to: .right, of: textView, withOffset: kToolbarMargin) sendButton.autoPinEdge(.bottom, to: .bottom, of: textView, withOffset: -3) @@ -1087,12 +1114,16 @@ class CaptioningToolbar: UIView, UITextViewDelegate { notImplemented() } - // MARK: + // MARK: - @objc func didTapSend() { self.captioningToolbarDelegate?.captioningToolbarDidTapSend(self) } + @objc func didTapAddMore() { + self.captioningToolbarDelegate?.captioningToolbarDidAddMore(self) + } + // MARK: - UITextViewDelegate public func textViewDidChange(_ textView: UITextView) { diff --git a/SignalMessaging/ViewControllers/OWSTableViewController.h b/SignalMessaging/ViewControllers/OWSTableViewController.h index 471b6c4eb..0185f88b2 100644 --- a/SignalMessaging/ViewControllers/OWSTableViewController.h +++ b/SignalMessaging/ViewControllers/OWSTableViewController.h @@ -139,6 +139,8 @@ typedef UITableViewCell *_Nonnull (^OWSTableCustomCellBlock)(void); - (void)presentFromViewController:(UIViewController *)fromViewController; +- (void)applyTheme; + @end NS_ASSUME_NONNULL_END diff --git a/SignalMessaging/Views/GalleryRailView.swift b/SignalMessaging/Views/GalleryRailView.swift index 85c1bcc86..42c72c854 100644 --- a/SignalMessaging/Views/GalleryRailView.swift +++ b/SignalMessaging/Views/GalleryRailView.swift @@ -33,7 +33,7 @@ public class GalleryRailCellView: UIView { addGestureRecognizer(tapGesture) } - required init?(coder aDecoder: NSCoder) { + public required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -112,7 +112,7 @@ public class GalleryRailView: UIView, GalleryRailCellViewDelegate { scrollView.autoPinEdgesToSuperviewMargins() } - required init?(coder aDecoder: NSCoder) { + public required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/SignalMessaging/Views/OWSButton.swift b/SignalMessaging/Views/OWSButton.swift index 4ecae71bd..9d51bf99d 100644 --- a/SignalMessaging/Views/OWSButton.swift +++ b/SignalMessaging/Views/OWSButton.swift @@ -19,7 +19,7 @@ public class OWSButton: UIButton { self.addTarget(self, action: #selector(didTap), for: .touchUpInside) } - required init?(coder aDecoder: NSCoder) { + public required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/SignalMessaging/attachments/SignalAttachment.swift b/SignalMessaging/attachments/SignalAttachment.swift index d4ad2c049..8f5320244 100644 --- a/SignalMessaging/attachments/SignalAttachment.swift +++ b/SignalMessaging/attachments/SignalAttachment.swift @@ -160,6 +160,10 @@ public class SignalAttachment: NSObject { @objc public let dataUTI: String + // Can be used by views to link this SignalAttachment with an Photos framework asset. + @objc + public var assetId: String? + var error: SignalAttachmentError? { didSet { AssertIsOnMainThread()