Added initial support for sharing URLs and text
Updated the share extension to load URL previews. Updated the ThreadPickerVC to send plain text & URLs in the same way they are sent for normal messages.
This commit is contained in:
parent
3c32ed7cc1
commit
dd9eeb5d61
|
@ -160,6 +160,10 @@ NSString *const ReportedApplicationStateDidChangeNotification = @"ReportedApplic
|
|||
return [UIApplication sharedApplication].applicationState == UIApplicationStateActive;
|
||||
}
|
||||
|
||||
- (BOOL)isShareExtension {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isRTL
|
||||
{
|
||||
static BOOL isRTL = NO;
|
||||
|
|
|
@ -114,6 +114,9 @@ public class SignalAttachment: NSObject {
|
|||
|
||||
@objc
|
||||
public var captionText: String?
|
||||
|
||||
@objc
|
||||
public var linkPreviewDraft: OWSLinkPreviewDraft?
|
||||
|
||||
@objc
|
||||
public var data: Data {
|
||||
|
@ -292,6 +295,15 @@ public class SignalAttachment: NSObject {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
public func text() -> String? {
|
||||
guard let text = String(data: dataSource.data(), encoding: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
// Returns the MIME type for this attachment or nil if no MIME type
|
||||
// can be identified.
|
||||
|
|
|
@ -9,6 +9,7 @@ final class NotificationServiceExtensionContext : NSObject, AppContext {
|
|||
let appLaunchTime = Date()
|
||||
let isMainApp = false
|
||||
let isMainAppAndActive = false
|
||||
var isShareExtension: Bool = false
|
||||
|
||||
var openSystemSettingsAction: UIAlertAction?
|
||||
var wasWokenUpByPushNotification = true
|
||||
|
|
|
@ -13,6 +13,7 @@ final class ShareAppExtensionContext: NSObject, AppContext {
|
|||
let appLaunchTime = Date()
|
||||
let isMainApp = false
|
||||
let isMainAppAndActive = false
|
||||
var isShareExtension: Bool = true
|
||||
|
||||
var mainWindow: UIWindow?
|
||||
var wasWokenUpByPushNotification: Bool = false
|
||||
|
|
|
@ -147,19 +147,44 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView
|
|||
}
|
||||
|
||||
func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didApproveAttachments attachments: [SignalAttachment], messageText: String?) {
|
||||
// Sharing a URL or plain text will populate the 'messageText' field so in those
|
||||
// cases we should ignore the attachments
|
||||
let isSharingUrl: Bool = (attachments.count == 1 && attachments[0].isUrl)
|
||||
let isSharingText: Bool = (attachments.count == 1 && attachments[0].isText)
|
||||
let finalAttachments: [SignalAttachment] = (isSharingUrl || isSharingText ? [] : attachments)
|
||||
|
||||
let message = VisibleMessage()
|
||||
message.sentTimestamp = NSDate.millisecondTimestamp()
|
||||
message.text = messageText
|
||||
|
||||
let tsMessage = TSOutgoingMessage.from(message, associatedWith: selectedThread!)
|
||||
Storage.write { transaction in
|
||||
tsMessage.save(with: transaction)
|
||||
}
|
||||
Storage.write(
|
||||
with: { transaction in
|
||||
if isSharingUrl {
|
||||
message.linkPreview = VisibleMessage.LinkPreview.from(
|
||||
attachments[0].linkPreviewDraft,
|
||||
using: transaction
|
||||
)
|
||||
}
|
||||
else {
|
||||
tsMessage.save(with: transaction)
|
||||
}
|
||||
},
|
||||
completion: {
|
||||
if isSharingUrl {
|
||||
tsMessage.linkPreview = OWSLinkPreview.from(message.linkPreview)
|
||||
|
||||
Storage.write { transaction in
|
||||
tsMessage.save(with: transaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
shareVC!.dismiss(animated: true, completion: nil)
|
||||
|
||||
ModalActivityIndicatorViewController.present(fromViewController: shareVC!, canCancel: false, message: "vc_share_sending_message".localized()) { activityIndicator in
|
||||
MessageSender.sendNonDurably(message, with: attachments, in: self.selectedThread!)
|
||||
MessageSender.sendNonDurably(message, with: finalAttachments, in: self.selectedThread!)
|
||||
.done { [weak self] _ in
|
||||
activityIndicator.dismiss { }
|
||||
self?.shareVC?.shareViewWasCompleted()
|
||||
|
|
|
@ -35,6 +35,7 @@ NSString *NSStringForUIApplicationState(UIApplicationState value);
|
|||
|
||||
@property (nonatomic, readonly) BOOL isMainApp;
|
||||
@property (nonatomic, readonly) BOOL isMainAppAndActive;
|
||||
@property (nonatomic, readonly) BOOL isShareExtension;
|
||||
/// Whether the app was woken up by a silent push notification. This is important for determining whether attachments should be downloaded or not.
|
||||
@property (nonatomic) BOOL wasWokenUpByPushNotification;
|
||||
|
||||
|
|
|
@ -594,7 +594,7 @@ open class ProxiedContentDownloader: NSObject, URLSessionTaskDelegate, URLSessio
|
|||
removeAssetRequestFromQueue(assetRequest: assetRequest)
|
||||
return
|
||||
}
|
||||
guard CurrentAppContext().isMainAppAndActive else {
|
||||
guard CurrentAppContext().isMainAppAndActive || CurrentAppContext().isShareExtension else {
|
||||
// If app is not active, fail the asset request.
|
||||
assetRequest.state = .failed
|
||||
assetRequestDidFail(assetRequest: assetRequest)
|
||||
|
|
|
@ -241,6 +241,12 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
|
|||
UIView.performWithoutAnimation {
|
||||
self.currentPageViewController?.view.layoutIfNeeded()
|
||||
}
|
||||
|
||||
// If the first item is just text, or is a URL and LinkPreviews are disabled
|
||||
// then just fill the 'message' box with it
|
||||
if firstItem.attachment.isText || (firstItem.attachment.isUrl && OWSLinkPreview.previewURL(forRawBodyText: firstItem.attachment.text()) == nil) {
|
||||
bottomToolView.attachmentTextToolbar.messageText = firstItem.attachment.text()
|
||||
}
|
||||
|
||||
setupLayout()
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
import Foundation
|
||||
import MediaPlayer
|
||||
import YYImage
|
||||
import NVActivityIndicatorView
|
||||
|
||||
import SessionUIKit
|
||||
|
||||
|
@ -52,6 +53,8 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
|
|||
|
||||
@objc
|
||||
public var contentView: UIView?
|
||||
|
||||
private var linkPreviewInfo: (url: String, draft: OWSLinkPreviewDraft?)?
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
|
@ -89,6 +92,10 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
|
|||
createVideoPreview()
|
||||
} else if attachment.isAudio {
|
||||
createAudioPreview()
|
||||
} else if attachment.isUrl {
|
||||
createUrlPreview()
|
||||
} else if attachment.isText {
|
||||
// Do nothing as we will just put the text in the 'message' input
|
||||
} else {
|
||||
createGenericPreview()
|
||||
}
|
||||
|
@ -118,6 +125,31 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
|
|||
|
||||
return stackView
|
||||
}
|
||||
|
||||
private func wrapViewsInHorizontalStack(subviews: [UIView]) -> UIView {
|
||||
assert(subviews.count > 0)
|
||||
|
||||
let stackView = UIView()
|
||||
|
||||
var lastView: UIView?
|
||||
for subview in subviews {
|
||||
|
||||
stackView.addSubview(subview)
|
||||
subview.autoVCenterInSuperview()
|
||||
|
||||
if lastView == nil {
|
||||
subview.autoPinEdge(toSuperviewEdge: .left)
|
||||
} else {
|
||||
subview.autoPinEdge(.left, to: .right, of: lastView!, withOffset: stackSpacing())
|
||||
}
|
||||
|
||||
lastView = subview
|
||||
}
|
||||
|
||||
lastView?.autoPinEdge(toSuperviewEdge: .right)
|
||||
|
||||
return stackView
|
||||
}
|
||||
|
||||
private func stackSpacing() -> CGFloat {
|
||||
switch mode {
|
||||
|
@ -265,6 +297,120 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
|
|||
videoPlayButton.autoSetDimension(.height, toSize: 72)
|
||||
}
|
||||
}
|
||||
|
||||
private func createUrlPreview() {
|
||||
// If link previews aren't enabled then use a fallback state
|
||||
guard let linkPreviewURL: String = OWSLinkPreview.previewURL(forRawBodyText: attachment.text()) else {
|
||||
createGenericPreview()
|
||||
return
|
||||
}
|
||||
|
||||
linkPreviewInfo = (url: linkPreviewURL, draft: nil)
|
||||
|
||||
var subviews = [UIView]()
|
||||
|
||||
let color: UIColor = isLightMode ? .black : .white
|
||||
let loadingView = NVActivityIndicatorView(frame: CGRect.zero, type: .circleStrokeSpin, color: color, padding: nil)
|
||||
loadingView.set(.width, to: 24)
|
||||
loadingView.set(.height, to: 24)
|
||||
loadingView.startAnimating()
|
||||
subviews.append(loadingView)
|
||||
|
||||
let imageViewContainer = UIView()
|
||||
imageViewContainer.clipsToBounds = true
|
||||
imageViewContainer.contentMode = .center
|
||||
imageViewContainer.alpha = 0
|
||||
imageViewContainer.layer.cornerRadius = 8
|
||||
subviews.append(imageViewContainer)
|
||||
|
||||
let imageView = createHeroImageView(imageName: "FileLarge")
|
||||
imageViewContainer.addSubview(imageView)
|
||||
imageView.pin(to: imageViewContainer)
|
||||
|
||||
let titleLabel = UILabel()
|
||||
titleLabel.text = linkPreviewURL
|
||||
titleLabel.textColor = controlTintColor
|
||||
titleLabel.font = labelFont()
|
||||
titleLabel.textAlignment = .center
|
||||
titleLabel.lineBreakMode = .byTruncatingMiddle
|
||||
subviews.append(titleLabel)
|
||||
|
||||
let stackView = wrapViewsInVerticalStack(subviews: subviews)
|
||||
self.addSubview(stackView)
|
||||
|
||||
titleLabel.autoPinWidthToSuperview(withMargin: 32)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
imageView.widthAnchor.constraint(equalToConstant: 80),
|
||||
imageView.heightAnchor.constraint(equalToConstant: 80)
|
||||
])
|
||||
|
||||
// Build the link preview
|
||||
OWSLinkPreview.tryToBuildPreviewInfo(previewUrl: linkPreviewURL).done { [weak self] draft in
|
||||
// Loader
|
||||
loadingView.alpha = 0
|
||||
loadingView.stopAnimating()
|
||||
|
||||
self?.linkPreviewInfo = (url: linkPreviewURL, draft: draft)
|
||||
|
||||
// TODO: Look at refactoring this behaviour to consolidate attachment mutations
|
||||
self?.attachment.linkPreviewDraft = draft
|
||||
|
||||
let image: UIImage?
|
||||
|
||||
if let jpegImageData: Data = draft.jpegImageData, let loadedImage: UIImage = UIImage(data: jpegImageData) {
|
||||
image = loadedImage
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
}
|
||||
else {
|
||||
image = UIImage(named: "Link")?.withTint(isLightMode ? .black : .white)
|
||||
imageView.contentMode = .center
|
||||
}
|
||||
|
||||
// Image view
|
||||
(imageView as? UIImageView)?.image = image
|
||||
imageViewContainer.alpha = 1
|
||||
imageViewContainer.backgroundColor = isDarkMode ? .black : UIColor.black.withAlphaComponent(0.06)
|
||||
|
||||
// Title
|
||||
if let title = draft.title {
|
||||
titleLabel.font = .boldSystemFont(ofSize: Values.smallFontSize)
|
||||
titleLabel.text = title
|
||||
titleLabel.textAlignment = .left
|
||||
titleLabel.numberOfLines = 2
|
||||
}
|
||||
|
||||
guard let hStackView = self?.wrapViewsInHorizontalStack(subviews: subviews) else {
|
||||
// TODO: Fallback
|
||||
return
|
||||
}
|
||||
stackView.removeFromSuperview()
|
||||
self?.addSubview(hStackView)
|
||||
|
||||
// We want to center the stackView in it's superview while also ensuring
|
||||
// it's superview is big enough to contain it.
|
||||
hStackView.autoPinWidthToSuperview(withMargin: 32)
|
||||
hStackView.autoVCenterInSuperview()
|
||||
NSLayoutConstraint.autoSetPriority(UILayoutPriority.defaultLow) {
|
||||
hStackView.autoPinHeightToSuperview()
|
||||
}
|
||||
hStackView.autoPinEdge(toSuperviewEdge: .top, withInset: 0, relation: .greaterThanOrEqual)
|
||||
hStackView.autoPinEdge(toSuperviewEdge: .bottom, withInset: 0, relation: .greaterThanOrEqual)
|
||||
}.catch { _ in
|
||||
// TODO: Fallback
|
||||
loadingView.stopAnimating()
|
||||
}.retainUntilComplete()
|
||||
|
||||
// We want to center the stackView in it's superview while also ensuring
|
||||
// it's superview is big enough to contain it.
|
||||
stackView.autoPinWidthToSuperview()
|
||||
stackView.autoVCenterInSuperview()
|
||||
NSLayoutConstraint.autoSetPriority(UILayoutPriority.defaultLow) {
|
||||
stackView.autoPinHeightToSuperview()
|
||||
}
|
||||
stackView.autoPinEdge(toSuperviewEdge: .top, withInset: 0, relation: .greaterThanOrEqual)
|
||||
stackView.autoPinEdge(toSuperviewEdge: .bottom, withInset: 0, relation: .greaterThanOrEqual)
|
||||
}
|
||||
|
||||
private func createGenericPreview() {
|
||||
var subviews = [UIView]()
|
||||
|
|
Loading…
Reference in New Issue