Sender Rail
This commit is contained in:
parent
1b759719e8
commit
87bfdbb72c
|
@ -446,14 +446,15 @@
|
|||
4C3EF802210918740007EBF7 /* SSKProtoEnvelopeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EF801210918740007EBF7 /* SSKProtoEnvelopeTest.swift */; };
|
||||
4C4AEC4520EC343B0020E72B /* DismissableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4AEC4420EC343B0020E72B /* DismissableTextField.swift */; };
|
||||
4C4BC6C32102D697004040C9 /* ContactDiscoveryOperationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4BC6C22102D697004040C9 /* ContactDiscoveryOperationTest.swift */; };
|
||||
4C618199219DF03A009BD6B5 /* OWSButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C618198219DF03A009BD6B5 /* OWSButton.swift */; };
|
||||
4C63CC00210A620B003AE45C /* SignalTSan.supp in Resources */ = {isa = PBXBuildFile; fileRef = 4C63CBFF210A620B003AE45C /* SignalTSan.supp */; };
|
||||
4C6F527C20FFE8400097DEEE /* SignalUBSan.supp in Resources */ = {isa = PBXBuildFile; fileRef = 4C6F527B20FFE8400097DEEE /* SignalUBSan.supp */; };
|
||||
4C7537892193779700DF5E37 /* OWS113MultiAttachmentMediaMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7537882193779700DF5E37 /* OWS113MultiAttachmentMediaMessages.swift */; };
|
||||
4C858A52212DC5E1001B45D3 /* UIImage+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C858A51212DC5E1001B45D3 /* UIImage+OWS.swift */; };
|
||||
4C948FF72146EB4800349F0D /* BlockListCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C948FF62146EB4800349F0D /* BlockListCache.swift */; };
|
||||
4C9CA25D217E676900607C63 /* ZXingObjC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C9CA25C217E676900607C63 /* ZXingObjC.framework */; };
|
||||
4CA46F4A219C78050038ABDE /* GalleryRailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA46F49219C78050038ABDE /* GalleryRailView.swift */; };
|
||||
4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA46F4B219CCC630038ABDE /* CaptionView.swift */; };
|
||||
4CA46F4D219CFDAA0038ABDE /* GalleryRailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA46F49219C78050038ABDE /* GalleryRailView.swift */; };
|
||||
4CA5F793211E1F06008C2708 /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA5F792211E1F06008C2708 /* Toast.swift */; };
|
||||
4CB5F26720F6E1E2004D1B42 /* MenuActionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF4C0920F55BBA005DA313 /* MenuActionsViewController.swift */; };
|
||||
4CB5F26920F7D060004D1B42 /* MessageActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB5F26820F7D060004D1B42 /* MessageActions.swift */; };
|
||||
|
@ -1158,6 +1159,7 @@
|
|||
4C3EF801210918740007EBF7 /* SSKProtoEnvelopeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKProtoEnvelopeTest.swift; sourceTree = "<group>"; };
|
||||
4C4AEC4420EC343B0020E72B /* DismissableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissableTextField.swift; sourceTree = "<group>"; };
|
||||
4C4BC6C22102D697004040C9 /* ContactDiscoveryOperationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ContactDiscoveryOperationTest.swift; path = contact/ContactDiscoveryOperationTest.swift; sourceTree = "<group>"; };
|
||||
4C618198219DF03A009BD6B5 /* OWSButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSButton.swift; sourceTree = "<group>"; };
|
||||
4C63CBFF210A620B003AE45C /* SignalTSan.supp */ = {isa = PBXFileReference; lastKnownFileType = text; path = SignalTSan.supp; sourceTree = "<group>"; };
|
||||
4C6F527B20FFE8400097DEEE /* SignalUBSan.supp */ = {isa = PBXFileReference; lastKnownFileType = text; path = SignalUBSan.supp; sourceTree = "<group>"; };
|
||||
4C7537882193779700DF5E37 /* OWS113MultiAttachmentMediaMessages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWS113MultiAttachmentMediaMessages.swift; sourceTree = "<group>"; };
|
||||
|
@ -1699,6 +1701,7 @@
|
|||
34AC09FC211B39E700997B47 /* ContactTableViewCell.h */,
|
||||
34AC09FF211B39E700997B47 /* ContactTableViewCell.m */,
|
||||
34AC0A00211B39E700997B47 /* DisappearingTimerConfigurationView.swift */,
|
||||
4CA46F49219C78050038ABDE /* GalleryRailView.swift */,
|
||||
34AC0A08211B39E900997B47 /* GradientView.swift */,
|
||||
34AC0A06211B39E900997B47 /* OWSAlerts.swift */,
|
||||
34AC0A09211B39E900997B47 /* OWSFlatButton.swift */,
|
||||
|
@ -1715,6 +1718,7 @@
|
|||
34AC0A0D211B39EA00997B47 /* ThreadViewHelper.h */,
|
||||
34AC0A0B211B39EA00997B47 /* ThreadViewHelper.m */,
|
||||
34AC0A04211B39E800997B47 /* VideoPlayerView.swift */,
|
||||
4C618198219DF03A009BD6B5 /* OWSButton.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
|
@ -2286,7 +2290,6 @@
|
|||
4CA5F792211E1F06008C2708 /* Toast.swift */,
|
||||
34B6A902218B3F62007C4606 /* TypingIndicatorView.swift */,
|
||||
4C1885D1218F8E1C00B67051 /* PhotoGridViewCell.swift */,
|
||||
4CA46F49219C78050038ABDE /* GalleryRailView.swift */,
|
||||
4CA46F4B219CCC630038ABDE /* CaptionView.swift */,
|
||||
);
|
||||
name = Views;
|
||||
|
@ -3247,6 +3250,7 @@
|
|||
342950822124C9750000B063 /* OWSTextField.m in Sources */,
|
||||
34AC0A13211B39EA00997B47 /* DisappearingTimerConfigurationView.swift in Sources */,
|
||||
3478506C1FD9B78A007B8332 /* NoopNotificationsManager.swift in Sources */,
|
||||
4CA46F4D219CFDAA0038ABDE /* GalleryRailView.swift in Sources */,
|
||||
34480B621FD0A98800BC14EF /* UIColor+OWS.m in Sources */,
|
||||
4C20B2B720CA0034001BAC90 /* ThreadViewModel.swift in Sources */,
|
||||
34480B641FD0A98800BC14EF /* UIView+OWS.m in Sources */,
|
||||
|
@ -3313,6 +3317,7 @@
|
|||
34B6D27520F664C900765BE2 /* OWSUnreadIndicator.m in Sources */,
|
||||
346129A61FD1F09100532771 /* OWSContactsManager.m in Sources */,
|
||||
4541B71D209D3B7A0008608F /* ContactShareViewModel.swift in Sources */,
|
||||
4C618199219DF03A009BD6B5 /* OWSButton.swift in Sources */,
|
||||
4598198F204E2F28009414F2 /* OWS108CallLoggingPreference.m in Sources */,
|
||||
34AC09F3211B39B100997B47 /* NewNonContactConversationViewController.m in Sources */,
|
||||
34AC09FA211B39B100997B47 /* SharingThreadPickerViewController.m in Sources */,
|
||||
|
@ -3469,7 +3474,6 @@
|
|||
45B5360E206DD8BB00D61655 /* UIResponder+OWS.swift in Sources */,
|
||||
45F659821E1BE77000444429 /* NonCallKitCallUIAdaptee.swift in Sources */,
|
||||
45AE48511E0732D6004D96C2 /* TurnServerInfo.swift in Sources */,
|
||||
4CA46F4A219C78050038ABDE /* GalleryRailView.swift in Sources */,
|
||||
34B3F8771E8DF1700035BE1A /* ContactsPicker.swift in Sources */,
|
||||
45C0DC1B1E68FE9000E04C47 /* UIApplication+OWS.swift in Sources */,
|
||||
45FBC5C81DF8575700E9B410 /* CallKitCallManager.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "x-shadow-12@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "x-shadow-12@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "x-shadow-12@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 299 B |
Binary file not shown.
After Width: | Height: | Size: 545 B |
Binary file not shown.
After Width: | Height: | Size: 879 B |
|
@ -90,7 +90,7 @@
|
|||
|
||||
@import Photos;
|
||||
|
||||
//#define FEATURE_FLAG_ALBUM_SEND_ENABLED;
|
||||
#define FEATURE_FLAG_ALBUM_SEND_ENABLED
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
|
|
@ -331,7 +331,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
|||
return
|
||||
}
|
||||
|
||||
galleryRailView.configure(itemProvider: currentItem.album, focusedItem: currentItem)
|
||||
galleryRailView.configureCellViews(itemProvider: currentItem.album, focusedItem: currentItem)
|
||||
}
|
||||
|
||||
// MARK: Actions
|
||||
|
@ -764,6 +764,27 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
|||
}
|
||||
}
|
||||
|
||||
extension MediaGalleryItem: GalleryRailItem {
|
||||
public var aspectRatio: CGFloat {
|
||||
return self.imageSize.aspectRatio
|
||||
}
|
||||
|
||||
public func getRailImage() -> Promise<UIImage> {
|
||||
let (guarantee, fulfill) = Guarantee<UIImage>.pending()
|
||||
if let image = self.thumbnailImage(async: { fulfill($0) }) {
|
||||
fulfill(image)
|
||||
}
|
||||
|
||||
return Promise(guarantee)
|
||||
}
|
||||
}
|
||||
|
||||
extension MediaGalleryAlbum: GalleryRailItemProvider {
|
||||
var railItems: [GalleryRailItem] {
|
||||
return self.items
|
||||
}
|
||||
}
|
||||
|
||||
extension MediaPageViewController: GalleryRailViewDelegate {
|
||||
func galleryRailView(_ galleryRailView: GalleryRailView, didTapItem imageRailItem: GalleryRailItem) {
|
||||
guard let targetItem = imageRailItem as? MediaGalleryItem else {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
import Foundation
|
||||
import AVFoundation
|
||||
import MediaPlayer
|
||||
import PromiseKit
|
||||
|
||||
@objc
|
||||
public protocol AttachmentApprovalViewControllerDelegate: class {
|
||||
|
@ -12,8 +13,82 @@ public protocol AttachmentApprovalViewControllerDelegate: class {
|
|||
func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didCancelAttachments attachments: [SignalAttachment])
|
||||
}
|
||||
|
||||
struct SignalAttachmentItem: Hashable {
|
||||
class AttachmentItemCollection {
|
||||
private (set) var attachmentItems: [SignalAttachmentItem]
|
||||
init(attachmentItems: [SignalAttachmentItem]) {
|
||||
self.attachmentItems = attachmentItems
|
||||
}
|
||||
|
||||
func itemAfter(item: SignalAttachmentItem) -> SignalAttachmentItem? {
|
||||
guard let currentIndex = attachmentItems.index(of: item) else {
|
||||
owsFailDebug("currentIndex was unexpectedly nil")
|
||||
return nil
|
||||
}
|
||||
|
||||
let nextIndex = attachmentItems.index(after: currentIndex)
|
||||
|
||||
return attachmentItems[safe: nextIndex]
|
||||
}
|
||||
|
||||
func itemBefore(item: SignalAttachmentItem) -> SignalAttachmentItem? {
|
||||
guard let currentIndex = attachmentItems.index(of: item) else {
|
||||
owsFailDebug("currentIndex was unexpectedly nil")
|
||||
return nil
|
||||
}
|
||||
|
||||
let prevIndex = attachmentItems.index(before: currentIndex)
|
||||
|
||||
return attachmentItems[safe: prevIndex]
|
||||
}
|
||||
|
||||
func remove(item: SignalAttachmentItem) {
|
||||
attachmentItems = attachmentItems.filter { $0 != item }
|
||||
}
|
||||
}
|
||||
|
||||
class SignalAttachmentItem: Hashable {
|
||||
|
||||
enum SignalAttachmentItemError: Error {
|
||||
case noThumbnail
|
||||
}
|
||||
|
||||
let attachment: SignalAttachment
|
||||
|
||||
init(attachment: SignalAttachment) {
|
||||
self.attachment = attachment
|
||||
}
|
||||
|
||||
// MARK:
|
||||
|
||||
var imageSize: CGSize = .zero
|
||||
|
||||
func getThumbnailImage() -> Promise<UIImage> {
|
||||
return DispatchQueue.global().async(.promise) { () -> UIImage in
|
||||
guard let image = self.attachment.image() else {
|
||||
throw SignalAttachmentItemError.noThumbnail
|
||||
}
|
||||
return image
|
||||
}.tap { result in
|
||||
switch result {
|
||||
case .fulfilled(let image):
|
||||
self.imageSize = image.size
|
||||
default: break
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Hashable
|
||||
|
||||
public var hashValue: Int {
|
||||
return attachment.hashValue
|
||||
}
|
||||
|
||||
// MARK: Equatable
|
||||
|
||||
static func == (lhs: SignalAttachmentItem, rhs: SignalAttachmentItem) -> Bool {
|
||||
return lhs.attachment == rhs.attachment
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
|
@ -37,7 +112,8 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
|
|||
@objc
|
||||
required public init(attachments: [SignalAttachment]) {
|
||||
assert(attachments.count > 0)
|
||||
self.attachmentItems = attachments.map { SignalAttachmentItem(attachment: $0 )}
|
||||
let attachmentItems = attachments.map { SignalAttachmentItem(attachment: $0 )}
|
||||
self.attachmentItemCollection = AttachmentItemCollection(attachmentItems: attachmentItems)
|
||||
super.init(transitionStyle: .scroll,
|
||||
navigationOrientation: .horizontal,
|
||||
options: [UIPageViewControllerOptionInterPageSpacingKey: kSpacingBetweenItems])
|
||||
|
@ -62,6 +138,9 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
|
|||
|
||||
// MARK: View Lifecycle
|
||||
|
||||
let galleryRailView = GalleryRailView()
|
||||
let railContainerView = UIView()
|
||||
|
||||
override public func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
|
@ -69,6 +148,27 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
|
|||
|
||||
disablePagingIfNecessary()
|
||||
|
||||
railContainerView.backgroundColor = UIColor.black.withAlphaComponent(0.6)
|
||||
view.addSubview(railContainerView)
|
||||
railContainerView.preservesSuperviewLayoutMargins = true
|
||||
railContainerView.layoutMargins.bottom = 50
|
||||
railContainerView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .top)
|
||||
|
||||
let footerGradientView = GradientView(from: .clear, to: .black)
|
||||
railContainerView.addSubview(footerGradientView)
|
||||
footerGradientView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .top)
|
||||
footerGradientView.autoSetDimension(.height, toSize: ScaleFromIPhone5(100))
|
||||
|
||||
railContainerView.addSubview(galleryRailView)
|
||||
galleryRailView.delegate = self
|
||||
galleryRailView.scrollFocusMode = .keepWithinBounds
|
||||
|
||||
galleryRailView.autoPinEdge(toSuperviewEdge: .leading)
|
||||
galleryRailView.autoPinEdge(toSuperviewEdge: .trailing)
|
||||
galleryRailView.autoPinEdge(toSuperviewMargin: .top)
|
||||
galleryRailView.autoPinEdge(toSuperviewMargin: .bottom)
|
||||
galleryRailView.autoSetDimension(.height, toSize: 72)
|
||||
|
||||
// Bottom Toolbar
|
||||
|
||||
let captioningToolbar = CaptioningToolbar()
|
||||
|
@ -124,6 +224,62 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
|
|||
|
||||
// MARK: - View Helpers
|
||||
|
||||
func remove(attachmentItem: SignalAttachmentItem) {
|
||||
if attachmentItem == currentItem {
|
||||
if let nextItem = attachmentItemCollection.itemAfter(item: attachmentItem) {
|
||||
setCurrentItem(nextItem, direction: .forward, animated: true)
|
||||
} else if let prevItem = attachmentItemCollection.itemBefore(item: attachmentItem) {
|
||||
setCurrentItem(prevItem, direction: .reverse, animated: true)
|
||||
} else {
|
||||
owsFailDebug("removing last item shouldn't be possible because rail should not be visible")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
guard let cell = galleryRailView.cellViews.first(where: { $0.item === attachmentItem }) else {
|
||||
owsFailDebug("cell was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
UIView.animate(withDuration: 0.2,
|
||||
animations: {
|
||||
// shrink stack view item until it disappears
|
||||
cell.isHidden = true
|
||||
|
||||
// simultaneously fade out
|
||||
cell.alpha = 0
|
||||
},
|
||||
completion: { _ in
|
||||
self.attachmentItemCollection.remove(item: attachmentItem)
|
||||
self.updateMediaRail()
|
||||
})
|
||||
}
|
||||
|
||||
func addDeleteIcon(cellViews: [GalleryRailCellView]) {
|
||||
for cellView in cellViews {
|
||||
guard let attachmentItem = cellView.item as? SignalAttachmentItem else {
|
||||
owsFailDebug("attachmentItem was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
let button = OWSButton { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.remove(attachmentItem: attachmentItem)
|
||||
}
|
||||
button.setImage(#imageLiteral(resourceName: "ic_small_x"), for: .normal)
|
||||
|
||||
let kInsetDistance: CGFloat = 5
|
||||
button.imageEdgeInsets = UIEdgeInsets(top: kInsetDistance, left: kInsetDistance, bottom: kInsetDistance, right: kInsetDistance)
|
||||
|
||||
cellView.addSubview(button)
|
||||
|
||||
let kButtonWidth: CGFloat = 9 + kInsetDistance * 2
|
||||
button.autoSetDimensions(to: CGSize(width: kButtonWidth, height: kButtonWidth))
|
||||
button.autoPinEdge(toSuperviewMargin: .top)
|
||||
button.autoPinEdge(toSuperviewMargin: .trailing)
|
||||
}
|
||||
}
|
||||
|
||||
var pagerScrollView: UIScrollView?
|
||||
// This is kind of a hack. Since we don't have first class access to the superview's `scrollView`
|
||||
// we traverse the view hierarchy until we find it, then disable scrolling if there's only one
|
||||
|
@ -195,6 +351,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
|
|||
},
|
||||
completion: nil)
|
||||
previousPage.zoomOut(animated: false)
|
||||
updateMediaRail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -274,10 +431,28 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
|
|||
}
|
||||
|
||||
self.setViewControllers([page], direction: direction, animated: isAnimated, completion: nil)
|
||||
// TODO update rail
|
||||
updateMediaRail()
|
||||
}
|
||||
|
||||
func updateMediaRail() {
|
||||
guard let currentItem = self.currentItem else {
|
||||
owsFailDebug("currentItem was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
galleryRailView.configureCellViews(itemProvider: attachmentItemCollection, focusedItem: currentItem)
|
||||
addDeleteIcon(cellViews: galleryRailView.cellViews)
|
||||
|
||||
railContainerView.isHidden = attachmentItemCollection.attachmentItems.count < 2
|
||||
captioningToolbar.alwaysShowGradient = railContainerView.isHidden
|
||||
}
|
||||
|
||||
let attachmentItemCollection: AttachmentItemCollection
|
||||
|
||||
var attachmentItems: [SignalAttachmentItem] {
|
||||
return attachmentItemCollection.attachmentItems
|
||||
}
|
||||
|
||||
let attachmentItems: [SignalAttachmentItem]
|
||||
var attachments: [SignalAttachment] {
|
||||
return attachmentItems.map { $0.attachment }
|
||||
}
|
||||
|
@ -348,6 +523,49 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: GalleryRail
|
||||
|
||||
extension SignalAttachmentItem: GalleryRailItem {
|
||||
var aspectRatio: CGFloat {
|
||||
return self.imageSize.aspectRatio
|
||||
}
|
||||
|
||||
func getRailImage() -> Promise<UIImage> {
|
||||
return self.getThumbnailImage()
|
||||
}
|
||||
}
|
||||
|
||||
extension AttachmentItemCollection: GalleryRailItemProvider {
|
||||
var railItems: [GalleryRailItem] {
|
||||
return self.attachmentItems
|
||||
}
|
||||
}
|
||||
|
||||
extension AttachmentApprovalViewController: GalleryRailViewDelegate {
|
||||
public func galleryRailView(_ galleryRailView: GalleryRailView, didTapItem imageRailItem: GalleryRailItem) {
|
||||
guard let targetItem = imageRailItem as? SignalAttachmentItem else {
|
||||
owsFailDebug("unexpected imageRailItem: \(imageRailItem)")
|
||||
return
|
||||
}
|
||||
|
||||
guard let currentIndex = attachmentItems.index(of: currentItem) else {
|
||||
owsFailDebug("currentIndex was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
guard let targetIndex = attachmentItems.index(of: targetItem) else {
|
||||
owsFailDebug("targetIndex was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
let direction: NavigationDirection = currentIndex < targetIndex ? .forward : .reverse
|
||||
|
||||
self.setCurrentItem(targetItem, direction: direction, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Individual Page
|
||||
|
||||
public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarDelegate, OWSVideoPlayerDelegate {
|
||||
// We sometimes shrink the attachment view so that it remains somewhat visible
|
||||
// when the keyboard is presented.
|
||||
|
@ -734,8 +952,8 @@ class CaptioningToolbar: UIView, UITextViewDelegate {
|
|||
set { self.textView.text = newValue }
|
||||
}
|
||||
|
||||
private let bottomGradient: GradientView
|
||||
private let lengthLimitLabel: UILabel
|
||||
private let bottomGradient: GradientView = GradientView(from: .clear, to: .black)
|
||||
private let lengthLimitLabel: UILabel = UILabel()
|
||||
|
||||
// Layout Constants
|
||||
|
||||
|
@ -770,10 +988,8 @@ class CaptioningToolbar: UIView, UITextViewDelegate {
|
|||
|
||||
init() {
|
||||
self.sendButton = UIButton(type: .system)
|
||||
self.bottomGradient = GradientView(from: UIColor.clear, to: UIColor.black)
|
||||
self.textView = MessageTextView()
|
||||
self.textViewHeight = kMinTextViewHeight
|
||||
self.lengthLimitLabel = UILabel()
|
||||
|
||||
super.init(frame: CGRect.zero)
|
||||
|
||||
|
@ -785,15 +1001,13 @@ class CaptioningToolbar: UIView, UITextViewDelegate {
|
|||
|
||||
textView.delegate = self
|
||||
textView.keyboardAppearance = Theme.keyboardAppearance
|
||||
textView.backgroundColor = (Theme.isDarkThemeEnabled ? UIColor.ows_gray90 : UIColor.ows_gray02)
|
||||
textView.layer.borderColor = (Theme.isDarkThemeEnabled
|
||||
? Theme.primaryColor.withAlphaComponent(0.06).cgColor
|
||||
: Theme.primaryColor.withAlphaComponent(0.12).cgColor)
|
||||
textView.backgroundColor = Theme.darkThemeBackgroundColor
|
||||
textView.layer.borderColor = Theme.darkThemePrimaryColor.cgColor
|
||||
textView.layer.borderWidth = 0.5
|
||||
textView.layer.cornerRadius = kMinTextViewHeight / 2
|
||||
|
||||
textView.font = UIFont.ows_dynamicTypeBody
|
||||
textView.textColor = Theme.primaryColor
|
||||
textView.textColor = Theme.darkThemePrimaryColor
|
||||
textView.returnKeyType = .done
|
||||
textView.textContainerInset = UIEdgeInsets(top: 7, left: 7, bottom: 7, right: 7)
|
||||
textView.scrollIndicatorInsets = UIEdgeInsets(top: 5, left: 0, bottom: 5, right: 3)
|
||||
|
@ -804,17 +1018,7 @@ class CaptioningToolbar: UIView, UITextViewDelegate {
|
|||
|
||||
sendButton.titleLabel?.font = UIFont.ows_mediumFont(withSize: 16)
|
||||
sendButton.titleLabel?.textAlignment = .center
|
||||
sendButton.tintColor = UIColor.white
|
||||
sendButton.backgroundColor = UIColor.ows_systemPrimaryButton
|
||||
sendButton.layer.cornerRadius = 4
|
||||
|
||||
// Send Button Shadow - without this the send button bottom doesn't feel aligned with the toolbar.
|
||||
let kSendButtonShadowOffset: CGFloat = 1
|
||||
sendButton.layer.shadowColor = UIColor.darkGray.cgColor
|
||||
sendButton.layer.shadowOffset = CGSize(width: 0, height: kSendButtonShadowOffset)
|
||||
sendButton.layer.shadowOpacity = 0.8
|
||||
sendButton.layer.shadowRadius = 0.0
|
||||
sendButton.layer.masksToBounds = false
|
||||
sendButton.tintColor = Theme.galleryHighlightColor
|
||||
|
||||
// Increase hit area of send button
|
||||
sendButton.contentEdgeInsets = UIEdgeInsets(top: 6, left: 8, bottom: 6, right: 8)
|
||||
|
@ -831,13 +1035,14 @@ class CaptioningToolbar: UIView, UITextViewDelegate {
|
|||
self.lengthLimitLabel.isHidden = true
|
||||
|
||||
let contentView = UIView()
|
||||
addSubview(contentView)
|
||||
contentView.autoPinEdgesToSuperviewEdges()
|
||||
contentView.addSubview(bottomGradient)
|
||||
contentView.addSubview(sendButton)
|
||||
contentView.addSubview(textView)
|
||||
contentView.addSubview(lengthLimitLabel)
|
||||
|
||||
addSubview(contentView)
|
||||
contentView.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
// Layout
|
||||
let kToolbarMargin: CGFloat = 8
|
||||
|
||||
|
@ -860,9 +1065,7 @@ class CaptioningToolbar: UIView, UITextViewDelegate {
|
|||
textView.autoPinEdge(toSuperviewMargin: .bottom)
|
||||
|
||||
sendButton.autoPinEdge(.left, to: .right, of: textView, withOffset: kToolbarMargin)
|
||||
|
||||
// Because the textview has a border, the sendButton feels unaligned without this shadow and offset
|
||||
sendButton.autoPinEdge(.bottom, to: .bottom, of: textView, withOffset: -kSendButtonShadowOffset)
|
||||
sendButton.autoPinEdge(.bottom, to: .bottom, of: textView, withOffset: -3)
|
||||
|
||||
sendButton.autoPinEdge(toSuperviewMargin: .right)
|
||||
sendButton.setContentHuggingHigh()
|
||||
|
@ -874,6 +1077,7 @@ class CaptioningToolbar: UIView, UITextViewDelegate {
|
|||
lengthLimitLabel.setContentHuggingHigh()
|
||||
lengthLimitLabel.setCompressionResistanceHigh()
|
||||
|
||||
bottomGradient.isHidden = true
|
||||
let bottomGradientHeight = ScaleFromIPhone5(100)
|
||||
bottomGradient.autoSetDimension(.height, toSize: bottomGradientHeight)
|
||||
bottomGradient.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .top)
|
||||
|
@ -931,12 +1135,30 @@ class CaptioningToolbar: UIView, UITextViewDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
var alwaysShowGradient: Bool = false {
|
||||
didSet {
|
||||
if alwaysShowGradient {
|
||||
bottomGradient.isHidden = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func textViewDidBeginEditing(_ textView: UITextView) {
|
||||
self.captioningToolbarDelegate?.captioningToolbarDidBeginEditing(self)
|
||||
if !alwaysShowGradient {
|
||||
UIView.animate(withDuration: 0.2) {
|
||||
self.bottomGradient.isHidden = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func textViewDidEndEditing(_ textView: UITextView) {
|
||||
self.captioningToolbarDelegate?.captioningToolbarDidEndEditing(self)
|
||||
if !alwaysShowGradient {
|
||||
UIView.animate(withDuration: 0.2) {
|
||||
self.bottomGradient.isHidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
|
|
@ -4,51 +4,20 @@
|
|||
|
||||
import PromiseKit
|
||||
|
||||
protocol GalleryRailItemProvider: class {
|
||||
public protocol GalleryRailItemProvider: class {
|
||||
var railItems: [GalleryRailItem] { get }
|
||||
}
|
||||
|
||||
protocol GalleryRailItem: class {
|
||||
func getRailImage() -> Guarantee<UIImage>
|
||||
public protocol GalleryRailItem: class {
|
||||
func getRailImage() -> Promise<UIImage>
|
||||
var aspectRatio: CGFloat { get }
|
||||
}
|
||||
|
||||
extension CGSize {
|
||||
var aspectRatio: CGFloat {
|
||||
guard self.height > 0 else {
|
||||
return 0
|
||||
}
|
||||
|
||||
return self.width / self.height
|
||||
}
|
||||
}
|
||||
|
||||
extension MediaGalleryItem: GalleryRailItem {
|
||||
var aspectRatio: CGFloat {
|
||||
return self.imageSize.aspectRatio
|
||||
}
|
||||
|
||||
func getRailImage() -> Guarantee<UIImage> {
|
||||
let (guarantee, fulfill) = Guarantee<UIImage>.pending()
|
||||
if let image = self.thumbnailImage(async: { fulfill($0) }) {
|
||||
fulfill(image)
|
||||
}
|
||||
|
||||
return guarantee
|
||||
}
|
||||
}
|
||||
|
||||
extension MediaGalleryAlbum: GalleryRailItemProvider {
|
||||
var railItems: [GalleryRailItem] {
|
||||
return self.items
|
||||
}
|
||||
}
|
||||
|
||||
protocol GalleryRailCellViewDelegate: class {
|
||||
func didTapGalleryRailCellView(_ galleryRailCellView: GalleryRailCellView)
|
||||
}
|
||||
|
||||
class GalleryRailCellView: UIView {
|
||||
public class GalleryRailCellView: UIView {
|
||||
|
||||
weak var delegate: GalleryRailCellViewDelegate?
|
||||
|
||||
|
@ -98,7 +67,7 @@ class GalleryRailCellView: UIView {
|
|||
self.isSelected = isSelected
|
||||
if isSelected {
|
||||
layoutMargins = UIEdgeInsets(top: 0, left: 6, bottom: 0, right: 6)
|
||||
imageView.layer.borderColor = UIColor(rgbHex: 0x1f8fe8).cgColor
|
||||
imageView.layer.borderColor = Theme.galleryHighlightColor.cgColor
|
||||
imageView.layer.borderWidth = 2
|
||||
imageView.layer.cornerRadius = 2
|
||||
} else {
|
||||
|
@ -120,13 +89,19 @@ class GalleryRailCellView: UIView {
|
|||
}()
|
||||
}
|
||||
|
||||
protocol GalleryRailViewDelegate: class {
|
||||
public protocol GalleryRailViewDelegate: class {
|
||||
func galleryRailView(_ galleryRailView: GalleryRailView, didTapItem imageRailItem: GalleryRailItem)
|
||||
}
|
||||
|
||||
class GalleryRailView: UIView, GalleryRailCellViewDelegate {
|
||||
public class GalleryRailView: UIView, GalleryRailCellViewDelegate {
|
||||
|
||||
weak var delegate: GalleryRailViewDelegate?
|
||||
public weak var delegate: GalleryRailViewDelegate?
|
||||
|
||||
public var cellViews: [GalleryRailCellView] = []
|
||||
|
||||
var cellViewItems: [GalleryRailItem] {
|
||||
get { return cellViews.compactMap { $0.item } }
|
||||
}
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
|
@ -143,13 +118,14 @@ class GalleryRailView: UIView, GalleryRailCellViewDelegate {
|
|||
|
||||
// MARK: Public
|
||||
|
||||
public func configure(itemProvider: GalleryRailItemProvider?, focusedItem: GalleryRailItem?) {
|
||||
public func configureCellViews(itemProvider: GalleryRailItemProvider?, focusedItem: GalleryRailItem?) {
|
||||
let animationDuration: TimeInterval = 0.2
|
||||
|
||||
guard let itemProvider = itemProvider else {
|
||||
UIView.animate(withDuration: animationDuration) {
|
||||
self.isHidden = true
|
||||
}
|
||||
self.cellViews = []
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -183,6 +159,7 @@ class GalleryRailView: UIView, GalleryRailCellViewDelegate {
|
|||
self.isHidden = true
|
||||
},
|
||||
completion: { _ in cellViews.forEach { $0.removeFromSuperview() } })
|
||||
self.cellViews = []
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -234,10 +211,10 @@ class GalleryRailView: UIView, GalleryRailCellViewDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
var cellViews: [GalleryRailCellView] = []
|
||||
var cellViewItems: [GalleryRailItem] {
|
||||
get { return cellViews.compactMap { $0.item } }
|
||||
enum ScrollFocusMode {
|
||||
case keepCentered, keepWithinBounds
|
||||
}
|
||||
var scrollFocusMode: ScrollFocusMode = .keepCentered
|
||||
func updateFocusedItem(_ focusedItem: GalleryRailItem?) {
|
||||
var selectedCellView: GalleryRailCellView?
|
||||
cellViews.forEach { cellView in
|
||||
|
@ -251,20 +228,42 @@ class GalleryRailView: UIView, GalleryRailCellViewDelegate {
|
|||
}
|
||||
|
||||
self.layoutIfNeeded()
|
||||
guard let selectedCell = selectedCellView else {
|
||||
owsFailDebug("selectedCell was unexpectedly nil")
|
||||
return
|
||||
switch scrollFocusMode {
|
||||
case .keepCentered:
|
||||
guard let selectedCell = selectedCellView else {
|
||||
owsFailDebug("selectedCell was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
let cellViewCenter = selectedCell.superview!.convert(selectedCell.center, to: scrollView)
|
||||
let additionalInset = scrollView.center.x - cellViewCenter.x
|
||||
|
||||
var inset = scrollView.contentInset
|
||||
inset.left = additionalInset
|
||||
scrollView.contentInset = inset
|
||||
|
||||
var offset = scrollView.contentOffset
|
||||
offset.x = -additionalInset
|
||||
scrollView.contentOffset = offset
|
||||
case .keepWithinBounds:
|
||||
guard let selectedCell = selectedCellView else {
|
||||
owsFailDebug("selectedCell was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
let cellFrame = selectedCell.superview!.convert(selectedCell.frame, to: scrollView)
|
||||
|
||||
scrollView.scrollRectToVisible(cellFrame, animated: true)
|
||||
}
|
||||
|
||||
let cellViewCenter = selectedCell.superview!.convert(selectedCell.center, to: scrollView)
|
||||
let additionalInset = scrollView.center.x - cellViewCenter.x
|
||||
|
||||
var inset = scrollView.contentInset
|
||||
inset.left = additionalInset
|
||||
scrollView.contentInset = inset
|
||||
|
||||
var offset = scrollView.contentOffset
|
||||
offset.x = -additionalInset
|
||||
scrollView.contentOffset = offset
|
||||
}
|
||||
}
|
||||
|
||||
public extension CGSize {
|
||||
var aspectRatio: CGFloat {
|
||||
guard self.height > 0 else {
|
||||
return 0
|
||||
}
|
||||
|
||||
return self.width / self.height
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@objc
|
||||
public class OWSButton: UIButton {
|
||||
|
||||
@objc
|
||||
var block: () -> Void = { }
|
||||
|
||||
// MARK: -
|
||||
|
||||
@objc
|
||||
init(block: @escaping () -> Void = { }) {
|
||||
super.init(frame: .zero)
|
||||
self.block = block
|
||||
self.addTarget(self, action: #selector(didTap), for: .touchUpInside)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
@objc
|
||||
func didTap() {
|
||||
block()
|
||||
}
|
||||
}
|
|
@ -41,6 +41,7 @@ extern NSString *const ThemeDidChangeNotification;
|
|||
|
||||
@property (class, readonly, nonatomic) UIColor *darkThemeBackgroundColor;
|
||||
@property (class, readonly, nonatomic) UIColor *darkThemePrimaryColor;
|
||||
@property (class, readonly, nonatomic) UIColor *galleryHighlightColor;
|
||||
|
||||
#pragma mark -
|
||||
|
||||
|
|
|
@ -128,6 +128,11 @@ NSString *const ThemeKeyThemeEnabled = @"ThemeKeyThemeEnabled";
|
|||
return UIColor.ows_gray05Color;
|
||||
}
|
||||
|
||||
+ (UIColor *)galleryHighlightColor
|
||||
{
|
||||
return [UIColor colorWithRGBHex:0x1f8fe8];
|
||||
}
|
||||
|
||||
+ (UIColor *)conversationButtonBackgroundColor
|
||||
{
|
||||
return (Theme.isDarkThemeEnabled ? [UIColor colorWithWhite:0.35f alpha:1.f] : UIColor.ows_gray02Color);
|
||||
|
|
Loading…
Reference in New Issue