Sender Rail

This commit is contained in:
Michael Kirk 2018-11-14 20:01:21 -06:00
parent 1b759719e8
commit 87bfdbb72c
12 changed files with 398 additions and 91 deletions

View File

@ -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 */,

View File

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

View File

@ -90,7 +90,7 @@
@import Photos;
//#define FEATURE_FLAG_ALBUM_SEND_ENABLED;
#define FEATURE_FLAG_ALBUM_SEND_ENABLED
NS_ASSUME_NONNULL_BEGIN

View File

@ -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 {

View File

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

View File

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

View File

@ -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()
}
}

View File

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

View File

@ -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);