Add "add more" button to image picker. Provide caption editing continuity.

pull/1/head
Matthew Chen 7 years ago
parent 2efbf1f432
commit 8b24fba095

@ -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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

@ -2676,7 +2676,7 @@ typedef enum : NSUInteger {
- (void)imagePicker:(OWSImagePickerGridController *)imagePicker - (void)imagePicker:(OWSImagePickerGridController *)imagePicker
didPickImageAttachments:(NSArray<SignalAttachment *> *)attachments didPickImageAttachments:(NSArray<SignalAttachment *> *)attachments
{ {
[self tryToSendAttachmentsIfApproved:attachments]; [self tryToSendAttachmentsIfApproved:attachments skipApprovalDialog:YES];
} }
/* /*

@ -12,7 +12,7 @@ protocol ImagePickerControllerDelegate {
} }
@objc(OWSImagePickerGridController) @objc(OWSImagePickerGridController)
class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegate, PhotoCollectionPickerDelegate { class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegate, PhotoCollectionPickerDelegate, AttachmentApprovalViewControllerDelegate {
@objc @objc
weak var delegate: ImagePickerControllerDelegate? weak var delegate: ImagePickerControllerDelegate?
@ -26,6 +26,11 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
private let titleLabel = UILabel() private let titleLabel = UILabel()
private var selectedIds = Set<String>()
// This variable should only be accessed on the main thread.
private var assetIdToCommentMap = [String: String]()
init() { init() {
collectionViewFlowLayout = type(of: self).buildLayout() collectionViewFlowLayout = type(of: self).buildLayout()
photoCollection = library.defaultPhotoCollection() photoCollection = library.defaultPhotoCollection()
@ -38,7 +43,7 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
// MARK: View Lifecycle // MARK: - View Lifecycle
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
@ -52,17 +57,27 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
collectionView.register(PhotoGridViewCell.self, forCellWithReuseIdentifier: PhotoGridViewCell.reuseIdentifier) collectionView.register(PhotoGridViewCell.self, forCellWithReuseIdentifier: PhotoGridViewCell.reuseIdentifier)
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, view.backgroundColor = .ows_gray95
target: self,
action: #selector(didPressCancel)) 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, *) { if #available(iOS 11, *) {
titleLabel.text = photoCollection.localizedTitle() titleLabel.text = photoCollection.localizedTitle()
titleLabel.textColor = Theme.primaryColor titleLabel.textColor = .ows_gray05
titleLabel.font = UIFont.ows_dynamicTypeBody.ows_mediumWeight() titleLabel.font = UIFont.ows_dynamicTypeBody.ows_mediumWeight()
let titleIconView = UIImageView() let titleIconView = UIImageView()
titleIconView.tintColor = Theme.primaryColor titleIconView.tintColor = .ows_gray05
titleIconView.image = UIImage(named: "navbar_disclosure_down")?.withRenderingMode(.alwaysTemplate) titleIconView.image = UIImage(named: "navbar_disclosure_down")?.withRenderingMode(.alwaysTemplate)
let titleView = UIStackView(arrangedSubviews: [titleLabel, titleIconView]) let titleView = UIStackView(arrangedSubviews: [titleLabel, titleIconView])
@ -81,7 +96,7 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
updateSelectButton() updateSelectButton()
} }
collectionView.backgroundColor = Theme.backgroundColor collectionView.backgroundColor = .ows_gray95
} }
override func viewWillLayoutSubviews() { override func viewWillLayoutSubviews() {
@ -96,16 +111,38 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
let scale = UIScreen.main.scale let scale = UIScreen.main.scale
let cellSize = collectionViewFlowLayout.itemSize let cellSize = collectionViewFlowLayout.itemSize
photoMediaSize.thumbnailSize = CGSize(width: cellSize.width * scale, height: cellSize.height * scale) 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..<count {
let asset = photoCollectionContents.asset(at: index)
let assetId = asset.localIdentifier
if selectedIds.contains(assetId) {
collectionView.selectItem(at: IndexPath(row: index, section: 0),
animated: false, scrollPosition: [])
}
}
} }
// MARK: Actions // MARK: - Actions
@objc @objc
func didPressCancel(sender: UIBarButtonItem) { func didPressCancel(sender: UIBarButtonItem) {
self.dismiss(animated: true) self.dismiss(animated: true)
} }
// MARK: Layout // MARK: - Layout
static let kInterItemSpacing: CGFloat = 2 static let kInterItemSpacing: CGFloat = 2
private class func buildLayout() -> UICollectionViewFlowLayout { private class func buildLayout() -> UICollectionViewFlowLayout {
@ -146,7 +183,7 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
} }
} }
// MARK: Batch Selection // MARK: - Batch Selection
lazy var doneButton: UIBarButtonItem = { lazy var doneButton: UIBarButtonItem = {
return UIBarButtonItem(barButtonSystemItem: .done, return UIBarButtonItem(barButtonSystemItem: .done,
@ -184,12 +221,39 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
} }
let assets: [PHAsset] = indexPaths.compactMap { return photoCollectionContents.asset(at: $0.row) } let assets: [PHAsset] = indexPaths.compactMap { return photoCollectionContents.asset(at: $0.row) }
let promises = assets.map { return photoCollectionContents.outgoingAttachment(for: $0) } complete(withAssets: assets)
when(fulfilled: promises).map { attachments in }
self.dismiss(animated: true) {
self.delegate?.imagePicker(self, didPickImageAttachments: attachments) func complete(withAssets assets: [PHAsset]) {
let attachmentPromises: [Promise<SignalAttachment>] = 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.sourceId else {
owsFailDebug("Attachment is missing asset id.")
continue
} }
}.retainUntilComplete() // Link the attachment with its asset to ensure caption continuity.
attachment.sourceId = 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() { func updateDoneButton() {
@ -206,7 +270,9 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
} }
func updateSelectButton() { func updateSelectButton() {
navigationItem.rightBarButtonItem = isInBatchSelectMode ? doneButton : selectButton let button = isInBatchSelectMode ? doneButton : selectButton
button.tintColor = .ows_gray05
navigationItem.rightBarButtonItem = button
} }
@objc @objc
@ -234,13 +300,17 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
collectionView.indexPathsForSelectedItems?.forEach { collectionView.deselectItem(at: $0, animated: false)} collectionView.indexPathsForSelectedItems?.forEach { collectionView.deselectItem(at: $0, animated: false)}
} }
// MARK: PhotoLibraryDelegate // MARK: - PhotoLibraryDelegate
func photoLibraryDidChange(_ photoLibrary: PhotoLibrary) { 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) { func photoCollectionPicker(_ photoCollectionPicker: PhotoCollectionPickerController, didPickCollection collection: PhotoCollection) {
photoCollection = collection photoCollection = collection
@ -252,7 +322,7 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
navigationItem.title = photoCollection.localizedTitle() navigationItem.title = photoCollection.localizedTitle()
} }
collectionView?.reloadData() reloadDataAndRestoreSelection()
} }
// MARK: - Event Handlers // MARK: - Event Handlers
@ -264,30 +334,33 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
let view = PhotoCollectionPickerController(library: library, let view = PhotoCollectionPickerController(library: library,
previousPhotoCollection: photoCollection, previousPhotoCollection: photoCollection,
collectionDelegate: self) collectionDelegate: self)
let nav = UINavigationController(rootViewController: view) let nav = OWSNavigationController(rootViewController: view)
self.present(nav, animated: true, completion: nil) self.present(nav, animated: true, completion: nil)
} }
// MARK: UICollectionView // MARK: - UICollectionView
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let asset = photoCollectionContents.asset(at: indexPath.item)
let assetId = asset.localIdentifier
selectedIds.insert(assetId)
if isInBatchSelectMode { if isInBatchSelectMode {
updateDoneButton() updateDoneButton()
} else { } else {
let asset = photoCollectionContents.asset(at: indexPath.row) let asset = photoCollectionContents.asset(at: indexPath.row)
firstly { complete(withAssets: [asset])
photoCollectionContents.outgoingAttachment(for: asset)
}.map { attachment in
self.dismiss(animated: true) {
self.delegate?.imagePicker(self, didPickImageAttachments: [attachment])
}
}.retainUntilComplete()
} }
} }
public override func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { public override func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
Logger.debug("") Logger.debug("")
let asset = photoCollectionContents.asset(at: indexPath.item)
let assetId = asset.localIdentifier
selectedIds.remove(assetId)
if isInBatchSelectMode { if isInBatchSelectMode {
updateDoneButton() updateDoneButton()
} }
@ -301,10 +374,44 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PhotoGridViewCell.reuseIdentifier, for: indexPath) as? PhotoGridViewCell else { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PhotoGridViewCell.reuseIdentifier, for: indexPath) as? PhotoGridViewCell else {
owsFail("cell was unexpectedly nil") owsFail("cell was unexpectedly nil")
} }
cell.loadingColor = UIColor(white: 0.2, alpha: 1)
let assetItem = photoCollectionContents.assetItem(at: indexPath.item, photoMediaSize: photoMediaSize) let assetItem = photoCollectionContents.assetItem(at: indexPath.item, photoMediaSize: photoMediaSize)
cell.configure(item: assetItem) cell.configure(item: assetItem)
let assetId = assetItem.asset.localIdentifier
let isSelected = selectedIds.contains(assetId)
cell.isSelected = isSelected
return cell 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.sourceId else {
owsFailDebug("Attachment missing source id.")
return
}
guard let captionText = attachment.captionText, captionText.count > 0 else {
assetIdToCommentMap.removeValue(forKey: assetId)
return
}
assetIdToCommentMap[assetId] = captionText
}
} }

@ -37,14 +37,22 @@ class PhotoCollectionPickerController: OWSTableViewController, PhotoLibraryDeleg
override func viewDidLoad() { override func viewDidLoad() {
super.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, *) { if #available(iOS 11, *) {
let titleLabel = UILabel() let titleLabel = UILabel()
titleLabel.text = previousPhotoCollection.localizedTitle() titleLabel.text = previousPhotoCollection.localizedTitle()
titleLabel.textColor = Theme.primaryColor titleLabel.textColor = .ows_gray05
titleLabel.font = UIFont.ows_dynamicTypeBody.ows_mediumWeight() titleLabel.font = UIFont.ows_dynamicTypeBody.ows_mediumWeight()
let titleIconView = UIImageView() let titleIconView = UIImageView()
titleIconView.tintColor = Theme.primaryColor titleIconView.tintColor = .ows_gray05
titleIconView.image = UIImage(named: "navbar_disclosure_up")?.withRenderingMode(.alwaysTemplate) titleIconView.image = UIImage(named: "navbar_disclosure_up")?.withRenderingMode(.alwaysTemplate)
let titleView = UIStackView(arrangedSubviews: [titleLabel, titleIconView]) let titleView = UIStackView(arrangedSubviews: [titleLabel, titleIconView])
@ -60,9 +68,11 @@ class PhotoCollectionPickerController: OWSTableViewController, PhotoLibraryDeleg
library.add(delegate: self) library.add(delegate: self)
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, let cancelButton = UIBarButtonItem(barButtonSystemItem: .stop,
target: self, target: self,
action: #selector(didPressCancel)) action: #selector(didPressCancel))
cancelButton.tintColor = .ows_gray05
navigationItem.leftBarButtonItem = cancelButton
updateContents() updateContents()
} }
@ -75,6 +85,10 @@ class PhotoCollectionPickerController: OWSTableViewController, PhotoLibraryDeleg
section.add(OWSTableItem.init(customCellBlock: { () -> UITableViewCell in section.add(OWSTableItem.init(customCellBlock: { () -> UITableViewCell in
let cell = OWSTableItem.newCell() 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 imageView = UIImageView()
let kImageSize = 50 let kImageSize = 50
imageView.autoSetDimensions(to: CGSize(width: kImageSize, height: kImageSize)) imageView.autoSetDimensions(to: CGSize(width: kImageSize, height: kImageSize))
@ -97,7 +111,7 @@ class PhotoCollectionPickerController: OWSTableViewController, PhotoLibraryDeleg
let titleLabel = UILabel() let titleLabel = UILabel()
titleLabel.text = collection.localizedTitle() titleLabel.text = collection.localizedTitle()
titleLabel.font = UIFont.ows_dynamicTypeBody titleLabel.font = UIFont.ows_dynamicTypeBody
titleLabel.textColor = Theme.primaryColor titleLabel.textColor = .ows_gray05
let stackView = UIStackView(arrangedSubviews: [imageView, titleLabel]) let stackView = UIStackView(arrangedSubviews: [imageView, titleLabel])
stackView.axis = .horizontal stackView.axis = .horizontal
@ -120,6 +134,12 @@ class PhotoCollectionPickerController: OWSTableViewController, PhotoLibraryDeleg
self.contents = contents self.contents = contents
} }
@objc
public override func applyTheme() {
view.backgroundColor = .ows_gray95
tableView.backgroundColor = .ows_gray95
}
// MARK: Actions // MARK: Actions
@objc @objc

@ -150,11 +150,15 @@ class PhotoCollectionContents {
switch asset.mediaType { switch asset.mediaType {
case .image: case .image:
return requestImageDataSource(for: asset).map { (dataSource: DataSource, dataUTI: String) in 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.sourceId = asset.localIdentifier
return attachment
} }
case .video: case .video:
return requestVideoDataSource(for: asset).map { (dataSource: DataSource, dataUTI: String) in 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.sourceId = asset.localIdentifier
return attachment
} }
default: default:
return Promise(error: PhotoLibraryError.unsupportedMediaType) return Promise(error: PhotoLibraryError.unsupportedMediaType)

@ -29,6 +29,8 @@ public class PhotoGridViewCell: UICollectionViewCell {
private static let animatedBadgeImage = #imageLiteral(resourceName: "ic_gallery_badge_gif") private static let animatedBadgeImage = #imageLiteral(resourceName: "ic_gallery_badge_gif")
private static let selectedBadgeImage = #imageLiteral(resourceName: "selected_blue_circle") private static let selectedBadgeImage = #imageLiteral(resourceName: "selected_blue_circle")
public var loadingColor = Theme.offBackgroundColor
override public var isSelected: Bool { override public var isSelected: Bool {
didSet { didSet {
self.selectedBadgeView.isHidden = !self.isSelected self.selectedBadgeView.isHidden = !self.isSelected
@ -99,7 +101,7 @@ public class PhotoGridViewCell: UICollectionViewCell {
get { return imageView.image } get { return imageView.image }
set { set {
imageView.image = newValue imageView.image = newValue
imageView.backgroundColor = newValue == nil ? Theme.offBackgroundColor : .clear imageView.backgroundColor = newValue == nil ? loadingColor : .clear
} }
} }

@ -11,6 +11,8 @@ import PromiseKit
public protocol AttachmentApprovalViewControllerDelegate: class { public protocol AttachmentApprovalViewControllerDelegate: class {
func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didApproveAttachments attachments: [SignalAttachment]) func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didApproveAttachments attachments: [SignalAttachment])
func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didCancelAttachments 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 { class AttachmentItemCollection {
@ -91,16 +93,24 @@ class SignalAttachmentItem: Hashable {
} }
} }
@objc
public enum AttachmentApprovalViewControllerMode: UInt {
case modal
case sharedNavigation
}
@objc @objc
public class AttachmentApprovalViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate, CaptioningToolbarDelegate { 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! private(set) var captioningToolbar: CaptioningToolbar!
// MARK: Initializers // MARK: - Initializers
@available(*, unavailable, message:"use attachment: constructor instead.") @available(*, unavailable, message:"use attachment: constructor instead.")
required public init?(coder aDecoder: NSCoder) { required public init?(coder aDecoder: NSCoder) {
@ -110,8 +120,10 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
let kSpacingBetweenItems: CGFloat = 20 let kSpacingBetweenItems: CGFloat = 20
@objc @objc
required public init(attachments: [SignalAttachment]) { required public init(mode: AttachmentApprovalViewControllerMode,
attachments: [SignalAttachment]) {
assert(attachments.count > 0) assert(attachments.count > 0)
self.mode = mode
let attachmentItems = attachments.map { SignalAttachmentItem(attachment: $0 )} let attachmentItems = attachments.map { SignalAttachmentItem(attachment: $0 )}
self.attachmentItemCollection = AttachmentItemCollection(attachmentItems: attachmentItems) self.attachmentItemCollection = AttachmentItemCollection(attachmentItems: attachmentItems)
super.init(transitionStyle: .scroll, super.init(transitionStyle: .scroll,
@ -123,7 +135,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
@objc @objc
public class func wrappedInNavController(attachments: [SignalAttachment], approvalDelegate: AttachmentApprovalViewControllerDelegate) -> OWSNavigationController { public class func wrappedInNavController(attachments: [SignalAttachment], approvalDelegate: AttachmentApprovalViewControllerDelegate) -> OWSNavigationController {
let vc = AttachmentApprovalViewController(attachments: attachments) let vc = AttachmentApprovalViewController(mode: .modal, attachments: attachments)
vc.approvalDelegate = approvalDelegate vc.approvalDelegate = approvalDelegate
let navController = OWSNavigationController(rootViewController: vc) let navController = OWSNavigationController(rootViewController: vc)
@ -136,7 +148,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
return navController return navController
} }
// MARK: View Lifecycle // MARK: - View Lifecycle
let galleryRailView = GalleryRailView() let galleryRailView = GalleryRailView()
let railContainerView = UIView() let railContainerView = UIView()
@ -171,7 +183,8 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
// Bottom Toolbar // Bottom Toolbar
let captioningToolbar = CaptioningToolbar() let isAddMoreVisible = mode == .sharedNavigation
let captioningToolbar = CaptioningToolbar(isAddMoreVisible: isAddMoreVisible)
captioningToolbar.captioningToolbarDelegate = self captioningToolbar.captioningToolbarDelegate = self
self.captioningToolbar = captioningToolbar self.captioningToolbar = captioningToolbar
@ -179,9 +192,12 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
self.navigationItem.title = nil self.navigationItem.title = nil
let cancelButton = UIBarButtonItem(barButtonSystemItem: .stop, target: self, action: #selector(cancelPressed)) if mode != .sharedNavigation {
cancelButton.tintColor = .white let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel,
self.navigationItem.leftBarButtonItem = cancelButton target: self, action: #selector(cancelPressed))
cancelButton.tintColor = .white
self.navigationItem.leftBarButtonItem = cancelButton
}
guard let firstItem = attachmentItems.first else { guard let firstItem = attachmentItems.first else {
owsFailDebug("firstItem was unexpectedly nil") owsFailDebug("firstItem was unexpectedly nil")
@ -189,6 +205,8 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
} }
self.setCurrentItem(firstItem, direction: .forward, animated: false) self.setCurrentItem(firstItem, direction: .forward, animated: false)
captioningToolbar.captionText = currentViewController.attachment.captionText
} }
override public func viewWillAppear(_ animated: Bool) { override public func viewWillAppear(_ animated: Bool) {
@ -263,8 +281,8 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
} }
let button = OWSButton { [weak self] in let button = OWSButton { [weak self] in
guard let self = self else { return } guard let strongSelf = self else { return }
self.remove(attachmentItem: attachmentItem) strongSelf.remove(attachmentItem: attachmentItem)
} }
button.setImage(#imageLiteral(resourceName: "ic_small_x"), for: .normal) button.setImage(#imageLiteral(resourceName: "ic_small_x"), for: .normal)
@ -300,21 +318,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
pagerScrollView.isScrollEnabled = attachmentItems.count > 1 pagerScrollView.isScrollEnabled = attachmentItems.count > 1
} }
private func makeClearToolbar() -> UIToolbar { // MARK: - UIPageViewControllerDelegate
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
public func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) { public func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
Logger.debug("") Logger.debug("")
@ -356,7 +360,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
} }
} }
// MARK: UIPageViewControllerDataSource // MARK: - UIPageViewControllerDataSource
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { public func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let currentViewController = viewController as? AttachmentPrepViewController else { guard let currentViewController = viewController as? AttachmentPrepViewController else {
@ -493,7 +497,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
self.approvalDelegate?.attachmentApproval(self, didCancelAttachments: attachments) self.approvalDelegate?.attachmentApproval(self, didCancelAttachments: attachments)
} }
// MARK: CaptioningToolbarDelegate // MARK: - CaptioningToolbarDelegate
var currentPageController: AttachmentPrepViewController { var currentPageController: AttachmentPrepViewController {
return viewControllers!.first as! AttachmentPrepViewController return viewControllers!.first as! AttachmentPrepViewController
@ -520,6 +524,12 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
func captioningToolbar(_ captioningToolbar: CaptioningToolbar, textViewDidChange textView: UITextView) { func captioningToolbar(_ captioningToolbar: CaptioningToolbar, textViewDidChange textView: UITextView) {
currentItem.attachment.captionText = textView.text 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 return
} }
let direction: NavigationDirection = currentIndex < targetIndex ? .forward : .reverse let direction: UIPageViewControllerNavigationDirection = currentIndex < targetIndex ? .forward : .reverse
self.setCurrentItem(targetItem, direction: direction, animated: true) self.setCurrentItem(targetItem, direction: direction, animated: true)
} }
@ -573,7 +583,7 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
case fullsize, compact case fullsize, compact
} }
// MARK: Properties // MARK: - Properties
let attachmentItem: SignalAttachmentItem let attachmentItem: SignalAttachmentItem
var attachment: SignalAttachment { var attachment: SignalAttachment {
@ -587,7 +597,7 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
private(set) var contentContainer: UIView! private(set) var contentContainer: UIView!
private(set) var playVideoButton: UIView? private(set) var playVideoButton: UIView?
// MARK: Initializers // MARK: - Initializers
init(attachmentItem: SignalAttachmentItem) { init(attachmentItem: SignalAttachmentItem) {
self.attachmentItem = attachmentItem self.attachmentItem = attachmentItem
@ -599,7 +609,7 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
// MARK: View Lifecycle // MARK: - View Lifecycle
override public func loadView() { override public func loadView() {
self.view = UIView() self.view = UIView()
@ -713,7 +723,7 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
ensureAttachmentViewScale(animated: false) ensureAttachmentViewScale(animated: false)
} }
// MARK: // MARK: -
@objc public func didTapPlayerView(_ gestureRecognizer: UIGestureRecognizer) { @objc public func didTapPlayerView(_ gestureRecognizer: UIGestureRecognizer) {
assert(self.videoPlayer != nil) assert(self.videoPlayer != nil)
@ -727,7 +737,7 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
self.playVideo() self.playVideo()
} }
// MARK: Video // MARK: - Video
private func playVideo() { private func playVideo() {
Logger.info("") Logger.info("")
@ -804,7 +814,7 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
} }
} }
// MARK: Helpers // MARK: - Helpers
var isZoomable: Bool { var isZoomable: Bool {
return attachment.isImage || attachment.isVideo return attachment.isImage || attachment.isVideo
@ -939,11 +949,13 @@ protocol CaptioningToolbarDelegate: class {
func captioningToolbarDidBeginEditing(_ captioningToolbar: CaptioningToolbar) func captioningToolbarDidBeginEditing(_ captioningToolbar: CaptioningToolbar)
func captioningToolbarDidEndEditing(_ captioningToolbar: CaptioningToolbar) func captioningToolbarDidEndEditing(_ captioningToolbar: CaptioningToolbar)
func captioningToolbar(_ captioningToolbar: CaptioningToolbar, textViewDidChange: UITextView) func captioningToolbar(_ captioningToolbar: CaptioningToolbar, textViewDidChange: UITextView)
func captioningToolbarDidAddMore(_ captioningToolbar: CaptioningToolbar)
} }
class CaptioningToolbar: UIView, UITextViewDelegate { class CaptioningToolbar: UIView, UITextViewDelegate {
weak var captioningToolbarDelegate: CaptioningToolbarDelegate? weak var captioningToolbarDelegate: CaptioningToolbarDelegate?
private let addMoreButton: UIButton
private let sendButton: UIButton private let sendButton: UIButton
private let textView: UITextView 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.sendButton = UIButton(type: .system)
self.textView = MessageTextView() self.textView = MessageTextView()
self.textViewHeight = kMinTextViewHeight self.textViewHeight = kMinTextViewHeight
@ -1012,6 +1025,9 @@ class CaptioningToolbar: UIView, UITextViewDelegate {
textView.textContainerInset = UIEdgeInsets(top: 7, left: 7, bottom: 7, right: 7) textView.textContainerInset = UIEdgeInsets(top: 7, left: 7, bottom: 7, right: 7)
textView.scrollIndicatorInsets = UIEdgeInsets(top: 5, left: 0, bottom: 5, right: 3) 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.") let sendTitle = NSLocalizedString("ATTACHMENT_APPROVAL_SEND_BUTTON", comment: "Label for 'send' button in the 'attachment approval' dialog.")
sendButton.setTitle(sendTitle, for: .normal) sendButton.setTitle(sendTitle, for: .normal)
sendButton.addTarget(self, action: #selector(didTapSend), for: .touchUpInside) sendButton.addTarget(self, action: #selector(didTapSend), for: .touchUpInside)
@ -1039,6 +1055,9 @@ class CaptioningToolbar: UIView, UITextViewDelegate {
contentView.addSubview(sendButton) contentView.addSubview(sendButton)
contentView.addSubview(textView) contentView.addSubview(textView)
contentView.addSubview(lengthLimitLabel) contentView.addSubview(lengthLimitLabel)
if isAddMoreVisible {
contentView.addSubview(addMoreButton)
}
addSubview(contentView) addSubview(contentView)
contentView.autoPinEdgesToSuperviewEdges() 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 // 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. // 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 // 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: .top)
textView.autoPinEdge(toSuperviewMargin: .bottom) 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(.left, to: .right, of: textView, withOffset: kToolbarMargin)
sendButton.autoPinEdge(.bottom, to: .bottom, of: textView, withOffset: -3) sendButton.autoPinEdge(.bottom, to: .bottom, of: textView, withOffset: -3)
@ -1087,12 +1114,16 @@ class CaptioningToolbar: UIView, UITextViewDelegate {
notImplemented() notImplemented()
} }
// MARK: // MARK: -
@objc func didTapSend() { @objc func didTapSend() {
self.captioningToolbarDelegate?.captioningToolbarDidTapSend(self) self.captioningToolbarDelegate?.captioningToolbarDidTapSend(self)
} }
@objc func didTapAddMore() {
self.captioningToolbarDelegate?.captioningToolbarDidAddMore(self)
}
// MARK: - UITextViewDelegate // MARK: - UITextViewDelegate
public func textViewDidChange(_ textView: UITextView) { public func textViewDidChange(_ textView: UITextView) {

@ -139,6 +139,8 @@ typedef UITableViewCell *_Nonnull (^OWSTableCustomCellBlock)(void);
- (void)presentFromViewController:(UIViewController *)fromViewController; - (void)presentFromViewController:(UIViewController *)fromViewController;
- (void)applyTheme;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

@ -33,7 +33,7 @@ public class GalleryRailCellView: UIView {
addGestureRecognizer(tapGesture) addGestureRecognizer(tapGesture)
} }
required init?(coder aDecoder: NSCoder) { public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
@ -112,7 +112,7 @@ public class GalleryRailView: UIView, GalleryRailCellViewDelegate {
scrollView.autoPinEdgesToSuperviewMargins() scrollView.autoPinEdgesToSuperviewMargins()
} }
required init?(coder aDecoder: NSCoder) { public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }

@ -19,7 +19,7 @@ public class OWSButton: UIButton {
self.addTarget(self, action: #selector(didTap), for: .touchUpInside) 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") fatalError("init(coder:) has not been implemented")
} }

@ -160,6 +160,10 @@ public class SignalAttachment: NSObject {
@objc @objc
public let dataUTI: String public let dataUTI: String
// Can be used by views to link this SignalAttachment with an arbitrary source.
@objc
public var sourceId: String?
var error: SignalAttachmentError? { var error: SignalAttachmentError? {
didSet { didSet {
AssertIsOnMainThread() AssertIsOnMainThread()

Loading…
Cancel
Save