diff --git a/Ring/Ring/Calls/Conference/ContactPickerViewController.storyboard b/Ring/Ring/Calls/Conference/ContactPickerViewController.storyboard index 2e3931788061ce2c6309f11b883cfab0e260ed05..b709a859d5d7dafde30dbd824eb4c8c7d3cc3bf9 100644 --- a/Ring/Ring/Calls/Conference/ContactPickerViewController.storyboard +++ b/Ring/Ring/Calls/Conference/ContactPickerViewController.storyboard @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> -<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097.3" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="ZwP-Qn-oLY"> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="ZwP-Qn-oLY"> <device id="retina6_1" orientation="portrait" appearance="light"/> <dependencies> <deployment identifier="iOS"/> - <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/> + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/> <capability name="Safe area layout guides" minToolsVersion="9.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> </dependencies> @@ -11,7 +11,7 @@ <!--Contact Picker View Controller--> <scene sceneID="QZd-Vi-EyD"> <objects> - <viewController modalPresentationStyle="overCurrentContext" id="ZwP-Qn-oLY" customClass="ContactPickerViewController" customModule="Ring" customModuleProvider="target" sceneMemberID="viewController"> + <viewController extendedLayoutIncludesOpaqueBars="YES" modalPresentationStyle="overFullScreen" id="ZwP-Qn-oLY" customClass="ContactPickerViewController" customModule="Ring" customModuleProvider="target" sceneMemberID="viewController"> <view key="view" contentMode="scaleToFill" id="TtT-WG-OAE"> <rect key="frame" x="0.0" y="0.0" width="414" height="896"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> @@ -40,10 +40,16 @@ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="Bdz-kj-0K4"> <rect key="frame" x="0.0" y="44" width="414" height="818"/> <subviews> + <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="LZ0-vA-uc5"> + <rect key="frame" x="182" y="0.0" width="50" height="50"/> + <constraints> + <constraint firstAttribute="height" constant="50" id="C1b-Mo-dnW"/> + </constraints> + </view> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="cfK-DS-kt1"> - <rect key="frame" x="0.0" y="0.0" width="414" height="90"/> + <rect key="frame" x="0.0" y="50" width="414" height="90"/> <subviews> - <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="6AM-a0-weG"> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="6AM-a0-weG"> <rect key="frame" x="364" y="45" width="30" height="35"/> <fontDescription key="fontDescription" type="system" pointSize="19"/> </button> @@ -55,7 +61,7 @@ </constraints> </view> <searchBar contentMode="redraw" searchBarStyle="prominent" showsSearchResultsButton="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ht5-JP-L4t"> - <rect key="frame" x="0.0" y="90" width="414" height="44"/> + <rect key="frame" x="0.0" y="140" width="414" height="44"/> <constraints> <constraint firstAttribute="height" constant="44" id="Ott-Go-5mf"/> </constraints> @@ -63,7 +69,7 @@ <textInputTraits key="textInputTraits"/> </searchBar> <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" allowsMultipleSelection="YES" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="i1P-Li-H82"> - <rect key="frame" x="0.0" y="134" width="414" height="684"/> + <rect key="frame" x="0.0" y="184" width="414" height="634"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <connections> <outlet property="delegate" destination="ZwP-Qn-oLY" id="EoQ-WF-bN4"/> @@ -71,7 +77,6 @@ </tableView> </subviews> <constraints> - <constraint firstItem="cfK-DS-kt1" firstAttribute="top" secondItem="Bdz-kj-0K4" secondAttribute="top" id="4Ai-6C-pS6"/> <constraint firstAttribute="trailing" secondItem="i1P-Li-H82" secondAttribute="trailing" id="4SM-vM-mgc"/> <constraint firstAttribute="bottom" secondItem="i1P-Li-H82" secondAttribute="bottom" id="MtY-d8-Qac"/> <constraint firstItem="cfK-DS-kt1" firstAttribute="leading" secondItem="Bdz-kj-0K4" secondAttribute="leading" id="RVA-PE-P1b"/> @@ -82,6 +87,7 @@ </constraints> </stackView> </subviews> + <viewLayoutGuide key="safeArea" id="HLr-8o-AJK"/> <constraints> <constraint firstItem="VI3-Wm-odB" firstAttribute="top" secondItem="Bdz-kj-0K4" secondAttribute="top" id="0qq-2Q-k11"/> <constraint firstItem="VI3-Wm-odB" firstAttribute="leading" secondItem="Bdz-kj-0K4" secondAttribute="leading" id="5pC-zY-2Vk"/> @@ -92,12 +98,12 @@ <constraint firstItem="HLr-8o-AJK" firstAttribute="bottom" secondItem="VI3-Wm-odB" secondAttribute="bottom" id="l3e-iB-s2n"/> <constraint firstItem="VI3-Wm-odB" firstAttribute="trailing" secondItem="Bdz-kj-0K4" secondAttribute="trailing" id="sVI-Ny-tp3"/> </constraints> - <viewLayoutGuide key="safeArea" id="HLr-8o-AJK"/> </view> <connections> <outlet property="doneButton" destination="6AM-a0-weG" id="2yX-Fh-qwe"/> <outlet property="searchBar" destination="ht5-JP-L4t" id="adL-r1-B3M"/> <outlet property="tableView" destination="i1P-Li-H82" id="Hbd-c3-3WV"/> + <outlet property="topSpace" destination="C1b-Mo-dnW" id="0UO-Q0-o1H"/> <outlet property="topViewContainer" destination="cfK-DS-kt1" id="s5H-k8-YXw"/> </connections> </viewController> diff --git a/Ring/Ring/Calls/Conference/ContactPickerViewController.swift b/Ring/Ring/Calls/Conference/ContactPickerViewController.swift index 8c85ad9655ad6ab15ba5a8170cd058957ecf1a46..1ceca8c4388571912b9e846008c5ce9ba43d0146 100644 --- a/Ring/Ring/Calls/Conference/ContactPickerViewController.swift +++ b/Ring/Ring/Calls/Conference/ContactPickerViewController.swift @@ -37,6 +37,7 @@ class ContactPickerViewController: UIViewController, StoryboardBased, ViewModelB @IBOutlet weak var tableView: UITableView! @IBOutlet weak var doneButton: UIButton! @IBOutlet weak var topViewContainer: UIView! + @IBOutlet weak var topSpace: NSLayoutConstraint! var viewModel: ContactPickerViewModel! private let disposeBag = DisposeBag() @@ -224,6 +225,7 @@ class ContactPickerViewController: UIViewController, StoryboardBased, ViewModelB let dismissGR = UISwipeGestureRecognizer(target: self, action: #selector(remove(gesture:))) dismissGR.direction = UISwipeGestureRecognizer.Direction.down dismissGR.delegate = self + topSpace.constant = 0 self.searchBar.addGestureRecognizer(dismissGR) self.rowSelectionHandler = { [weak self] row in guard let self = self else { return } @@ -236,6 +238,7 @@ class ContactPickerViewController: UIViewController, StoryboardBased, ViewModelB self.searchBar.backgroundColor = UIColor.clear self.doneButton.setTitle(L10n.Actions.cancelAction, for: .normal) self.doneButton.setTitleColor(UIColor.jamiTextBlue, for: .normal) + topSpace.constant = 50 self.doneButton.rx.tap .subscribe(onNext: { [weak self] in let paths = self?.tableView.indexPathsForSelectedRows diff --git a/Ring/Ring/Constants/Generated/Images.swift b/Ring/Ring/Constants/Generated/Images.swift index f346b31378146f3f63df729a0317903168303904..adc0e747356c78e476ef5c9790f57ca1b30ba0e9 100644 --- a/Ring/Ring/Constants/Generated/Images.swift +++ b/Ring/Ring/Constants/Generated/Images.swift @@ -50,7 +50,10 @@ internal enum Asset { internal static let icBack = ImageAsset(name: "ic_back") internal static let icContactPicture = ImageAsset(name: "ic_contact_picture") internal static let icConversationRemove = ImageAsset(name: "ic_conversation_remove") + internal static let icForward = ImageAsset(name: "ic_forward") internal static let icHideInput = ImageAsset(name: "ic_hide_input") + internal static let icSave = ImageAsset(name: "ic_save") + internal static let icShare = ImageAsset(name: "ic_share") internal static let icShowInput = ImageAsset(name: "ic_show_input") internal static let infoArrow = ImageAsset(name: "info_arrow") internal static let jamiIcon = ImageAsset(name: "jamiIcon") diff --git a/Ring/Ring/Constants/Generated/Strings.swift b/Ring/Ring/Constants/Generated/Strings.swift index 532c2bb4e76329113b28c6358622b87278c4611d..17e9c272b6b564347cd6f63c01f17823c5badb44 100644 --- a/Ring/Ring/Constants/Generated/Strings.swift +++ b/Ring/Ring/Constants/Generated/Strings.swift @@ -321,6 +321,8 @@ internal enum L10n { } internal enum Conversation { + /// Failed to save image to galery + internal static let errorSavingImage = L10n.tr("Localizable", "conversation.errorSavingImage") /// You are currently receiving a live location from internal static let explanationReceivingLocationFrom = L10n.tr("Localizable", "conversation.explanationReceivingLocationFrom") /// You are currently sharing your location with @@ -451,12 +453,20 @@ internal enum L10n { internal static let close = L10n.tr("Localizable", "global.close") /// Invitations internal static let contactRequestsTabBarTitle = L10n.tr("Localizable", "global.contactRequestsTabBarTitle") + /// Forward + internal static let forward = L10n.tr("Localizable", "global.forward") /// Conversations internal static let homeTabBarTitle = L10n.tr("Localizable", "global.homeTabBarTitle") /// Account internal static let meTabBarTitle = L10n.tr("Localizable", "global.meTabBarTitle") /// Ok internal static let ok = L10n.tr("Localizable", "global.ok") + /// Preview + internal static let preview = L10n.tr("Localizable", "global.preview") + /// Resend + internal static let resend = L10n.tr("Localizable", "global.resend") + /// Save + internal static let save = L10n.tr("Localizable", "global.save") /// Share internal static let share = L10n.tr("Localizable", "global.share") /// Together diff --git a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCell.swift b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCell.swift index c54f98c387999300fcceea55c0813f2d2d4cd388..36579cbc4e965f387755f2eec282e059aec2806a 100644 --- a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCell.swift +++ b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCell.swift @@ -30,8 +30,7 @@ import SwiftyBeaver // swiftlint:disable type_body_length // swiftlint:disable file_length -class MessageCell: UITableViewCell, NibReusable, PlayerDelegate { - +class MessageCell: UITableViewCell, NibReusable, PlayerDelegate, PreviewViewControllerDelegate { // MARK: Properties let log = SwiftyBeaver.self @@ -76,10 +75,17 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate { private(set) var messageId: Int64? private var isCopyable: Bool = false private var couldBeShared: Bool = false + private var couldBeResend: Bool = false + private var couldBeForward: Bool = false + private var couldBeSaved: Bool = false private let _deleteMessage = BehaviorRelay<Bool>(value: false) var deleteMessage: Observable<Bool> { _deleteMessage.asObservable() } var shareMessage = PublishSubject<Bool>() + var forwardMessage = PublishSubject<Bool>() + var saveMessage = PublishSubject<Bool>() + var resendMessage = PublishSubject<Bool>() + var previewMessage = PublishSubject<Bool>() private let showTimeTap = BehaviorRelay<Bool>(value: false) var tappedToShowTime: Observable<Bool> { showTimeTap.asObservable() } @@ -134,6 +140,8 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate { self.messageId = nil self.isCopyable = false self.couldBeShared = false + self.couldBeResend = false + self.couldBeForward = false self._deleteMessage.accept(false) if let longGestureRecognizer = longGestureRecognizer { self.bubble.removeGestureRecognizer(longGestureRecognizer) @@ -207,21 +215,17 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate { } } - func supportFullScreenMode() -> Bool { - return (playerView != nil && playerView!.viewModel.hasVideo.value) || self.transferImageView.image != nil - } - // MARK: Configure func configureTapGesture() { - let shownByDefault = !self.timeLabel.isHidden && !showTimeTap.value && !supportFullScreenMode() + let shownByDefault = !self.timeLabel.isHidden && !showTimeTap.value && !self.couldBeShared if shownByDefault { return } self.bubble.isUserInteractionEnabled = true self.tapGestureRecognizer = UITapGestureRecognizer() self.tapGestureRecognizer?.numberOfTapsRequired = 1 self.tapGestureRecognizer?.delegate = self self.tapGestureRecognizer!.rx.event.bind(onNext: { [weak self] _ in self?.onTapGesture() }).disposed(by: self.disposeBag) - self.tapGestureRecognizer!.cancelsTouchesInView = supportFullScreenMode() ? true : false + self.tapGestureRecognizer!.cancelsTouchesInView = self.couldBeShared ? true : false self.bubble.addGestureRecognizer(tapGestureRecognizer!) guard let doubleTap = doubleTapGestureRecognizer else { return } self.tapGestureRecognizer?.require(toFail: doubleTap) @@ -245,7 +249,7 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate { func onTapGesture() { // for player or image expand size on tap, for other messages show time - if supportFullScreenMode() { + if self.couldBeShared { openPreview.accept(true) return } @@ -261,10 +265,13 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate { self.showTimeTap.accept(true) } - private func configureLongGesture(_ messageId: Int64, _ bubblePosition: BubblePosition, _ isTransfer: Bool, _ isLocationSharingBubble: Bool) { + private func configureLongGesture(_ messageId: Int64, _ bubblePosition: BubblePosition, _ isTransfer: Bool, _ isLocationSharingBubble: Bool, isTransferSuccess: Bool, isError: Bool) { self.messageId = messageId self.isCopyable = bubblePosition != .generated && !isTransfer && !isLocationSharingBubble - self.couldBeShared = bubblePosition != .generated && !isLocationSharingBubble + self.couldBeShared = isTransfer && isTransferSuccess + self.couldBeForward = bubblePosition != .generated && !isLocationSharingBubble && (isTransferSuccess || !isTransfer) + self.couldBeResend = isError && bubblePosition == .sent + self.couldBeSaved = self.couldBeShared && self.transferedImage != nil self.bubble.isUserInteractionEnabled = true longGestureRecognizer = UILongPressGestureRecognizer() longGestureRecognizer!.rx.event.bind(onNext: { [weak self] _ in self?.onLongGesture() }).disposed(by: self.disposeBag) @@ -275,7 +282,11 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate { becomeFirstResponder() let menu = UIMenuController.shared let shareItem = UIMenuItem(title: L10n.Global.share, action: NSSelectorFromString("share")) - menu.menuItems = [shareItem] + let forwardItem = UIMenuItem(title: L10n.Global.forward, action: NSSelectorFromString("forward")) + let saveItem = UIMenuItem(title: L10n.Global.save, action: NSSelectorFromString("save")) + let resendItem = UIMenuItem(title: L10n.Global.resend, action: NSSelectorFromString("resend")) + let previewItem = UIMenuItem(title: L10n.Global.preview, action: NSSelectorFromString("preview")) + menu.menuItems = [previewItem, shareItem, forwardItem, saveItem, resendItem] if !menu.isMenuVisible { menu.setTargetRect(self.bubble.frame, in: self) menu.setMenuVisible(true, animated: true) @@ -287,6 +298,42 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate { shareMessage.onNext(true) } + @objc + func forward() { + forwardMessage.onNext(true) + } + + @objc + func save() { + saveMessage.onNext(true) + } + + @objc + func resend() { + resendMessage.onNext(true) + } + + @objc + func preview() { + previewMessage.onNext(true) + } + + func deleteFile() { + _deleteMessage.accept(true) + } + + func shareFile() { + shareMessage.onNext(true) + } + + func forwardFile() { + forwardMessage.onNext(true) + } + + func saveFile() { + saveMessage.onNext(true) + } + override func copy(_ sender: Any?) { UIPasteboard.general.string = self.messageLabel?.text UIMenuController.shared.setMenuVisible(false, animated: true) @@ -302,7 +349,12 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate { override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { return action == #selector(UIResponderStandardEditActions.copy) && self.isCopyable || - action == #selector(UIResponderStandardEditActions.delete) || (action == NSSelectorFromString("share") && self.couldBeShared) + action == #selector(UIResponderStandardEditActions.delete) || + (action == NSSelectorFromString("forward") && self.couldBeForward) || + (action == NSSelectorFromString("share") && self.couldBeShared) || + (action == NSSelectorFromString("resend") && self.couldBeResend) || + (action == NSSelectorFromString("save") && self.couldBeSaved) || + (action == NSSelectorFromString("preview") && self.couldBeShared) } func toggleCellTimeLabelVisibility() { @@ -441,7 +493,6 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate { self.configureCellTimeLabel(item) self.prepareForReuseLongGesture() - self.configureLongGesture(item.message.messageId, item.bubblePosition(), item.isTransfer, item.isLocationSharingBubble) self.prepareForReuseTapGesture() @@ -522,7 +573,16 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate { } else if item.isTransfer { self.messageLabelMarginConstraint.constant = -2 } - + var isError = false + if item.isTransfer { + isError = item.initialTransferStatus == .error || item.initialTransferStatus == .canceled + } else if item.isText { + isError = item.message.status == .failure + } + self.configureLongGesture(item.message.messageId, item.bubblePosition(), + item.isTransfer, item.isLocationSharingBubble, + isTransferSuccess: item.initialTransferStatus == .success, + isError: isError) self.configureTapGesture() } diff --git a/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.swift b/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.swift index e55a3dd700f4136611b6367d59d245e30a771e6a..efc268fd3704cf9b11af48fe334c7b6ef159d1b6 100644 --- a/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.swift +++ b/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.swift @@ -34,7 +34,9 @@ import MobileCoreServices // swiftlint:disable type_body_length class ConversationViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, - UIDocumentPickerDelegate, StoryboardBased, ViewModelBased, MessageAccessoryViewDelegate, ContactPickerDelegate, PHPickerViewControllerDelegate { + UIDocumentPickerDelegate, StoryboardBased, ViewModelBased, + MessageAccessoryViewDelegate, ContactPickerDelegate, + PHPickerViewControllerDelegate, UIDocumentInteractionControllerDelegate { let log = SwiftyBeaver.self @@ -73,18 +75,6 @@ class ConversationViewController: UIViewController, self.setupUI() self.setupTableView() self.setupBindings() - NotificationCenter.default.rx - .notification(UIDevice.orientationDidChangeNotification) - .observeOn(MainScheduler.instance) - .subscribe(onNext: {[weak self](_) in - guard let self = self, - UIDevice.current.portraitOrLandscape else { return } - self.setupNavTitle(profileImageData: self.viewModel.profileImageData.value, - displayName: self.viewModel.displayName.value, - username: self.viewModel.userName.value) - self.tableView.reloadData() - }) - .disposed(by: self.disposeBag) /* Register to keyboard notifications to adjust tableView insets when the keybaord appears @@ -100,6 +90,22 @@ class ConversationViewController: UIViewController, keyboardDismissTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) } + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + // Waiting for screen size change + DispatchQueue.global(qos: .background).async { + sleep(UInt32(0.5)) + DispatchQueue.main.async { [weak self] in + guard let self = self, + UIDevice.current.portraitOrLandscape else { return } + self.setupNavTitle(profileImageData: self.viewModel.profileImageData.value, + displayName: self.viewModel.displayName.value, + username: self.viewModel.userName.value) + self.tableView.reloadData() + } + } + super.viewWillTransition(to: size, with: coordinator) + } + @objc private func applicationWillResignActive() { self.viewModel.setIsComposingMsg(isComposing: false) @@ -137,6 +143,8 @@ class ConversationViewController: UIViewController, self.present(alert, animated: true, completion: nil) } + // MARK: photo library + func checkPhotoLibraryPermission() { let status = PHPhotoLibrary.authorizationStatus() switch status { @@ -367,6 +375,19 @@ class ConversationViewController: UIViewController, }) } + func saveImageToGalery (image: UIImage) { + UIImageWriteToSavedPhotosAlbum(image, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil) + } + + @objc + func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) { + if let error = error { + let allert = UIAlertController(title: L10n.Conversation.errorSavingImage, message: error.localizedDescription, preferredStyle: .alert) + allert.addAction(UIAlertAction(title: "OK", style: .default)) + present(allert, animated: true) + } + } + @objc func dismissKeyboard() { self.becomeFirstResponder() @@ -954,18 +975,38 @@ class ConversationViewController: UIViewController, .disposed(by: cell.disposeBag) } + // MARK: open file + + func openDocument(messageModel: MessageViewModel) { + let conversation = self.viewModel.conversation.value.conversationId + let accountId = self.viewModel.conversation.value.accountId + guard let url = messageModel.transferedFile(conversationID: conversation, accountId: accountId), + FileManager().fileExists(atPath: url.path) else { return } + let interactionController = UIDocumentInteractionController(url: url) + interactionController.delegate = self + interactionController.presentPreview(animated: true) + } + + func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController { + if let navigationController = self.navigationController { + return navigationController + } + return self + } + + // MARK: open share menu func showShareMenu(messageModel: MessageViewModel) { let conversation = self.viewModel.conversation.value.conversationId let accountId = self.viewModel.conversation.value.accountId if let file = messageModel.transferedFile(conversationID: conversation, accountId: accountId) { - self.presentActivitycontrollerWithItems(items: [file]) + self.presentActivityControllerWithItems(items: [file]) return } if messageModel .getURLFromPhotoLibrary(conversationID: conversation, completionHandler: { [weak self, weak messageModel] url in if let url = url { - self?.presentActivitycontrollerWithItems(items: [url]) + self?.presentActivityControllerWithItems(items: [url]) return } guard let messageModel = messageModel else { return } @@ -980,16 +1021,15 @@ class ConversationViewController: UIViewController, if let image = messageModel.getTransferedImage(maxSize: 250, conversationID: conversationId, accountId: accountId) { - self.presentActivitycontrollerWithItems(items: [image]) + self.presentActivityControllerWithItems(items: [image]) } } - func presentActivitycontrollerWithItems(items: [Any]) { + func presentActivityControllerWithItems(items: [Any]) { let activityViewController = UIActivityViewController(activityItems: items, applicationActivities: nil) activityViewController.popoverPresentationController?.sourceView = self.view activityViewController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection() activityViewController.popoverPresentationController?.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.maxX, width: 0, height: 0) - activityViewController.excludedActivityTypes = [UIActivity.ActivityType.airDrop] self.present(activityViewController, animated: true, completion: nil) } @@ -1001,8 +1041,8 @@ class ConversationViewController: UIViewController, let screenSize = UIScreen.main.bounds let screenWidth = screenSize.width let screenHeight = screenSize.height - let newFrame = CGRect(x: 0, y: -statusBarHeight, width: screenWidth, height: screenHeight) - let initialFrame = CGRect(x: 0, y: screenHeight, width: screenWidth, height: screenHeight) + let newFrame = CGRect(x: 0, y: -statusBarHeight, width: screenWidth, height: screenHeight + statusBarHeight) + let initialFrame = CGRect(x: 0, y: screenHeight, width: screenWidth, height: screenHeight + statusBarHeight) contactPickerVC.view.frame = initialFrame self.view.addSubview(contactPickerVC.view) contactPickerVC.didMove(toParent: self) @@ -1063,7 +1103,7 @@ extension ConversationViewController: UITableViewDataSource { self.transferCellSetup(item, cell, tableView, indexPath) self.locationCellSetup(item, cell) self.deleteCellSetup(cell) - self.shareMessageCellSeUp(cell, item: item) + self.messageCellActionsSetUp(cell, item: item) self.tapToShowTimeCellSetup(cell) return cell @@ -1089,12 +1129,47 @@ extension ConversationViewController: UITableViewDataSource { .disposed(by: cell.disposeBag) } - private func shareMessageCellSeUp(_ cell: MessageCell, item: MessageViewModel) { + private func messageCellActionsSetUp(_ cell: MessageCell, item: MessageViewModel) { cell.shareMessage + .observeOn(MainScheduler.instance) + .subscribe(onNext: { [weak self, weak item] (shouldShare) in + guard shouldShare, let item = item else { return } + self?.showShareMenu(messageModel: item) + }) + .disposed(by: cell.disposeBag) + cell.forwardMessage + .observeOn(MainScheduler.instance) + .subscribe(onNext: { [weak self, weak item] (shouldForward) in + guard shouldForward, let item = item else { return } + self?.viewModel.slectContactsToShareMessage(message: item) + }) + .disposed(by: cell.disposeBag) + cell.saveMessage + .observeOn(MainScheduler.instance) + .subscribe(onNext: { [weak self, weak cell] (shouldSave) in + guard shouldSave, let cell = cell, let image = cell.transferedImage else { return } + self?.saveImageToGalery(image: image) + }) + .disposed(by: cell.disposeBag) + cell.resendMessage .observeOn(MainScheduler.instance) .subscribe(onNext: { [weak self, weak item] (shouldResend) in guard shouldResend, let item = item else { return } - self?.viewModel.slectContactsToShareMessage(message: item) + self?.viewModel.resendMessage(message: item) + }) + .disposed(by: cell.disposeBag) + cell.previewMessage + .observeOn(MainScheduler.instance) + .subscribe(onNext: { [weak self, weak item, weak cell] (shouldPreview) in + guard shouldPreview, let item = item, let cell = cell, let self = self, let initialFrame = cell.getInitialFrame() else { return } + let player = item.getPlayer(conversationViewModel: self.viewModel) + let image = cell.transferedImage + if player == nil && image == nil { + self.openDocument(messageModel: item) + return + } + self.inputAccessoryView.isHidden = true + self.viewModel.openFullScreenPreview(parentView: self, viewModel: player, image: image, initialFrame: initialFrame, delegate: cell) }) .disposed(by: cell.disposeBag) } @@ -1196,13 +1271,16 @@ extension ConversationViewController: UITableViewDataSource { .disposed(by: cell.disposeBag) cell.openPreview .subscribe(onNext: { [weak self, weak item, weak cell] open in - guard let self = self, open, - let initialFrame = cell?.getInitialFrame() else { return } - let player = item?.getPlayer(conversationViewModel: self.viewModel) - let image = cell?.transferedImage - if player == nil && image == nil { return } + guard let self = self, open, let cell = cell, let item = item, + let initialFrame = cell.getInitialFrame() else { return } + let player = item.getPlayer(conversationViewModel: self.viewModel) + let image = cell.transferedImage + if player == nil && image == nil { + self.openDocument(messageModel: item) + return + } self.inputAccessoryView.isHidden = true - self.viewModel.openFullScreenPreview(parentView: self, viewModel: player, image: image, initialFrame: initialFrame) + self.viewModel.openFullScreenPreview(parentView: self, viewModel: player, image: image, initialFrame: initialFrame, delegate: cell) }) .disposed(by: cell.disposeBag) cell.playerHeight diff --git a/Ring/Ring/Features/Conversations/Conversation/ConversationViewModel.swift b/Ring/Ring/Features/Conversations/Conversation/ConversationViewModel.swift index 932969b547b648413905fee08cf704a4be51e2d9..20f8fd5e9939b41a3396be6cde21805dea41cf95 100644 --- a/Ring/Ring/Features/Conversations/Conversation/ConversationViewModel.swift +++ b/Ring/Ring/Features/Conversations/Conversation/ConversationViewModel.swift @@ -703,8 +703,8 @@ extension ConversationViewModel { contactUri: self.conversation.value.participantUri) } - func openFullScreenPreview(parentView: UIViewController, viewModel: PlayerViewModel?, image: UIImage?, initialFrame: CGRect) { - self.stateSubject.onNext(ConversationState.openFullScreenPreview(parentView: parentView, viewModel: viewModel, image: image, initialFrame: initialFrame)) + func openFullScreenPreview(parentView: UIViewController, viewModel: PlayerViewModel?, image: UIImage?, initialFrame: CGRect, delegate: PreviewViewControllerDelegate) { + self.stateSubject.onNext(ConversationState.openFullScreenPreview(parentView: parentView, viewModel: viewModel, image: image, initialFrame: initialFrame, delegate: delegate)) } } @@ -764,6 +764,36 @@ extension ConversationViewModel { self.changeConversationIfNeeded(items: selectedContacts) } + func resendMessage(message: MessageViewModel) { + guard !message.message.isGenerated, + !message.message.isLocationSharing else { return } + if !message.message.isTransfer { + self.sendMessage(withContent: message.content, contactURI: conversation.value.participantUri) + return + } + let conversationId = self.conversation.value.conversationId + let accountId = self.conversation.value.accountId + var fileName = message.content + if message.content.contains("\n") { + guard let substring = message.content.split(separator: "\n").first else { return } + fileName = String(substring) + } + if let url = message.transferedFile(conversationID: conversationId, accountId: accountId) { + self.sendFile(filePath: url.path, displayName: fileName, contactHash: self.conversation.value.hash) + return + } + if let image = message.getTransferedImage(maxSize: 200, conversationID: conversationId, accountId: accountId) { + let identifier = message.transferFileData.identifier + if identifier != nil { + self.sendImageFromPhotoLibraty(image: image, imageName: fileName, localIdentifier: identifier, contactHash: self.conversation.value.hash) + return + } + if let data = image.jpegData(compressionQuality: 100) { + self.sendAndSaveFile(displayName: fileName, imageData: data, contactHash: self.conversation.value.hash, conversation: self.conversation.value.conversationId) + } + } + } + func slectContactsToShareMessage(message: MessageViewModel) { guard !message.message.isGenerated, !message.message.isLocationSharing else { return } diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageViewModel.swift b/Ring/Ring/Features/Conversations/Conversation/MessageViewModel.swift index c401b65f908877a902f363761451fae12188d5d8..2ffb909e346bb2161be89c9b6c68383a24b3ec6b 100644 --- a/Ring/Ring/Features/Conversations/Conversation/MessageViewModel.swift +++ b/Ring/Ring/Features/Conversations/Conversation/MessageViewModel.swift @@ -68,6 +68,7 @@ class MessageViewModel { var isComposingIndicator: Bool = false var isLocationSharingBubble: Bool { return self.message.isLocationSharing } + var isText: Bool { return !self.message.isLocationSharing && !self.message.isGenerated && !self.message.isTransfer } private let disposeBag = DisposeBag() let injectBug: InjectionBag diff --git a/Ring/Ring/Features/Conversations/Preview/PreviewViewController.storyboard b/Ring/Ring/Features/Conversations/Preview/PreviewViewController.storyboard index eebbc9187d337225a9f60d12bd9c3c849383111a..221f9d69ec5a6831db3d36c82f273f9314843e14 100644 --- a/Ring/Ring/Features/Conversations/Preview/PreviewViewController.storyboard +++ b/Ring/Ring/Features/Conversations/Preview/PreviewViewController.storyboard @@ -1,10 +1,12 @@ <?xml version="1.0" encoding="UTF-8"?> -<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097.3" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="edr-AU-dxH"> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="edr-AU-dxH"> <device id="retina6_1" orientation="portrait" appearance="light"/> <dependencies> <deployment identifier="iOS"/> - <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/> + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/> + <capability name="Image references" minToolsVersion="12.0"/> <capability name="Safe area layout guides" minToolsVersion="9.0"/> + <capability name="System colors in document resources" minToolsVersion="11.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> </dependencies> <scenes> @@ -23,6 +25,41 @@ <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="kNJ-67-zFf"> <rect key="frame" x="0.0" y="0.0" width="414" height="896"/> </imageView> + <stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" alignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="3aB-Lr-pJQ"> + <rect key="frame" x="0.0" y="772" width="414" height="90"/> + <subviews> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Gvs-6O-5Fs"> + <rect key="frame" x="0.0" y="33" width="103.5" height="24"/> + <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <state key="normal"> + <imageReference key="image" image="ic_share" symbolScale="large"/> + </state> + </button> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="GFG-Jq-Pqx"> + <rect key="frame" x="103.5" y="31.5" width="103.5" height="27"/> + <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <state key="normal"> + <imageReference key="image" image="ic_forward" symbolScale="large"/> + </state> + </button> + <button opaque="NO" contentMode="center" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="5q1-cC-MI7"> + <rect key="frame" x="207" y="31.5" width="103.5" height="27"/> + <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <state key="normal" image="ic_save"> + <preferredSymbolConfiguration key="preferredSymbolConfiguration" scale="default"/> + </state> + </button> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="5pB-7e-B3j"> + <rect key="frame" x="310.5" y="31.5" width="103.5" height="27"/> + <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <state key="normal" image="ic_conversation_remove"/> + </button> + </subviews> + <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.7987211045" colorSpace="custom" customColorSpace="sRGB"/> + <constraints> + <constraint firstAttribute="height" constant="90" id="BTB-tx-Psf"/> + </constraints> + </stackView> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="umc-Uj-AVs"> <rect key="frame" x="0.0" y="0.0" width="414" height="80"/> <constraints> @@ -32,21 +69,25 @@ <view contentMode="scaleToFill" id="Jga-0U-qXG" customClass="PlayerView" customModule="Ring" customModuleProvider="target"> <rect key="frame" x="0.0" y="0.0" width="414" height="896"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> - <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> + <color key="backgroundColor" systemColor="systemBackgroundColor"/> </view> - <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="s0z-cK-ISY"> - <rect key="frame" x="340" y="44" width="49" height="35"/> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="s0z-cK-ISY"> + <rect key="frame" x="341" y="44" width="48" height="35"/> <fontDescription key="fontDescription" type="system" pointSize="19"/> <state key="normal" title="Close"> <color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> </state> </button> </subviews> + <viewLayoutGuide key="safeArea" id="dJP-6X-zM4"/> <constraints> <constraint firstItem="s0z-cK-ISY" firstAttribute="top" secondItem="dJP-6X-zM4" secondAttribute="top" priority="250" id="0CU-8Q-b0S"/> + <constraint firstItem="3aB-Lr-pJQ" firstAttribute="leading" secondItem="dJP-6X-zM4" secondAttribute="leading" id="26g-Gx-ObU"/> <constraint firstItem="9Qb-6X-Utl" firstAttribute="leading" secondItem="QAC-jd-TeH" secondAttribute="leading" id="39d-qj-71g"/> <constraint firstItem="s0z-cK-ISY" firstAttribute="trailing" secondItem="Jga-0U-qXG" secondAttribute="trailing" constant="-25" id="IcL-2k-lX8"/> + <constraint firstItem="dJP-6X-zM4" firstAttribute="trailing" secondItem="3aB-Lr-pJQ" secondAttribute="trailing" id="Mj0-Nc-ouz"/> <constraint firstItem="Jga-0U-qXG" firstAttribute="top" secondItem="QAC-jd-TeH" secondAttribute="top" id="Ome-jS-TQa"/> + <constraint firstItem="dJP-6X-zM4" firstAttribute="bottom" secondItem="3aB-Lr-pJQ" secondAttribute="bottom" id="QMX-S4-F1Q"/> <constraint firstAttribute="bottom" secondItem="9Qb-6X-Utl" secondAttribute="bottom" id="R1V-fE-JGu"/> <constraint firstItem="umc-Uj-AVs" firstAttribute="top" secondItem="QAC-jd-TeH" secondAttribute="top" id="RVi-sm-LCM"/> <constraint firstItem="umc-Uj-AVs" firstAttribute="leading" secondItem="dJP-6X-zM4" secondAttribute="leading" id="XwX-7d-vQ6"/> @@ -61,12 +102,14 @@ <constraint firstItem="Jga-0U-qXG" firstAttribute="leading" secondItem="dJP-6X-zM4" secondAttribute="leading" id="lQU-Ek-RAG"/> <constraint firstItem="9Qb-6X-Utl" firstAttribute="top" secondItem="QAC-jd-TeH" secondAttribute="top" id="nIf-wb-wOx"/> </constraints> - <viewLayoutGuide key="safeArea" id="dJP-6X-zM4"/> </view> <nil key="simulatedTopBarMetrics"/> <nil key="simulatedBottomBarMetrics"/> <connections> <outlet property="backgroundView" destination="9Qb-6X-Utl" id="nva-VK-fcG"/> + <outlet property="buttonsContainer" destination="3aB-Lr-pJQ" id="u7z-BG-n68"/> + <outlet property="deleteButton" destination="5pB-7e-B3j" id="Fcj-G9-OLd"/> + <outlet property="forwardButton" destination="GFG-Jq-Pqx" id="Vot-bV-hkU"/> <outlet property="gradientView" destination="umc-Uj-AVs" id="q4m-Ib-c0V"/> <outlet property="hideButton" destination="s0z-cK-ISY" id="PeP-TI-Y2O"/> <outlet property="imageBottomConstraint" destination="Z3A-GV-6Py" id="4dp-wF-Ix2"/> @@ -75,6 +118,8 @@ <outlet property="imageTrailingConstraint" destination="Zxa-bY-zaY" id="gac-QR-lKz"/> <outlet property="imageView" destination="kNJ-67-zFf" id="rTG-bA-Gz6"/> <outlet property="playerView" destination="Jga-0U-qXG" id="nNg-vJ-wZp"/> + <outlet property="saveButton" destination="5q1-cC-MI7" id="QG0-Vq-ETD"/> + <outlet property="shareButton" destination="Gvs-6O-5Fs" id="FNx-Hm-Hav"/> </connections> </viewController> <placeholder placeholderIdentifier="IBFirstResponder" id="5Ku-db-uak" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/> @@ -82,4 +127,13 @@ <point key="canvasLocation" x="137.68115942028987" y="96.428571428571431"/> </scene> </scenes> + <resources> + <image name="ic_conversation_remove" width="27" height="27"/> + <image name="ic_forward" width="27" height="27"/> + <image name="ic_save" width="27" height="27"/> + <image name="ic_share" width="24" height="24"/> + <systemColor name="systemBackgroundColor"> + <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + </systemColor> + </resources> </document> diff --git a/Ring/Ring/Features/Conversations/Preview/PreviewViewController.swift b/Ring/Ring/Features/Conversations/Preview/PreviewViewController.swift index 0d55efeb26c1fef200be52c83a195e6bb943fa35..a70802744239ef133f6df2c6a90cd945a7631155 100644 --- a/Ring/Ring/Features/Conversations/Preview/PreviewViewController.swift +++ b/Ring/Ring/Features/Conversations/Preview/PreviewViewController.swift @@ -27,6 +27,13 @@ enum PrevewType { case image } +protocol PreviewViewControllerDelegate: class { + func deleteFile() + func shareFile() + func forwardFile() + func saveFile() +} + class PreviewViewController: UIViewController, StoryboardBased, ViewModelBased { // MARK: - outlets @IBOutlet weak var playerView: PlayerView! @@ -38,12 +45,18 @@ class PreviewViewController: UIViewController, StoryboardBased, ViewModelBased { @IBOutlet weak var imageBottomConstraint: NSLayoutConstraint! @IBOutlet weak var backgroundView: UIView! @IBOutlet weak var gradientView: UIView! +@IBOutlet weak var shareButton: UIButton! +@IBOutlet weak var deleteButton: UIButton! +@IBOutlet weak var forwardButton: UIButton! +@IBOutlet weak var saveButton: UIButton! +@IBOutlet weak var buttonsContainer: UIStackView! // MARK: - members let disposeBag = DisposeBag() var viewModel: PreviewControllerModel! var tapGestureRecognizer: UITapGestureRecognizer! var type: PrevewType = .player + weak var delegate: PreviewViewControllerDelegate? override func viewDidLoad() { super.viewDidLoad() @@ -67,8 +80,41 @@ class PreviewViewController: UIViewController, StoryboardBased, ViewModelBased { .disposed(by: self.disposeBag) self.hideButton.centerYAnchor.constraint(equalTo: self.playerView.muteAudio.centerYAnchor, constant: 0).isActive = true self.hideButton.setTitle(L10n.Global.close, for: .normal) + self.shareButton.isUserInteractionEnabled = self.type == .image + self.deleteButton.isUserInteractionEnabled = self.type == .image + self.forwardButton.isUserInteractionEnabled = self.type == .image + buttonsContainer.isHidden = self.type != .image if self.type == .image, let image = self.viewModel.image { self.imageView.image = image + let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(startZooming(_:))) + imageView.isUserInteractionEnabled = true + imageView.addGestureRecognizer(pinchGesture) + self.shareButton.rx.tap + .subscribe(onNext: { [weak self] in + self?.share() + }) + .disposed(by: self.disposeBag) + self.deleteButton.rx.tap + .subscribe(onNext: { [weak self] in + self?.parent?.inputAccessoryView?.isHidden = false + self?.removeChildController() + self?.delete() + }) + .disposed(by: self.disposeBag) + self.forwardButton.rx.tap + .subscribe(onNext: { [weak self] in + self?.forward() + self?.parent?.inputAccessoryView?.isHidden = false + self?.removeChildController() + }) + .disposed(by: self.disposeBag) + self.saveButton.rx.tap + .subscribe(onNext: { [weak self] in + self?.parent?.inputAccessoryView?.isHidden = false + self?.removeChildController() + self?.save() + }) + .disposed(by: self.disposeBag) return } guard let model = self.viewModel.playerViewModel, let playerView = playerView else { return } @@ -77,6 +123,14 @@ class PreviewViewController: UIViewController, StoryboardBased, ViewModelBased { self.view.addGestureRecognizer(tapGestureRecognizer) } + @objc + private func startZooming(_ sender: UIPinchGestureRecognizer) { + let scaleResult = sender.view?.transform.scaledBy(x: sender.scale, y: sender.scale) + guard let scale = scaleResult, scale.a > 1, scale.d > 1 else { return } + sender.view?.transform = scale + sender.scale = 1 + } + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.navigationController?.setNavigationBarHidden(true, animated: animated) @@ -120,4 +174,25 @@ class PreviewViewController: UIViewController, StoryboardBased, ViewModelBased { self.view.layoutIfNeeded() }, completion: nil) } + func share() { + if let delegate = self.delegate { + delegate.shareFile() + } + } + func delete() { + if let delegate = self.delegate { + delegate.deleteFile() + } + } + func forward() { + if let delegate = self.delegate { + delegate.forwardFile() + } + } + + func save() { + if let delegate = self.delegate { + delegate.saveFile() + } + } } diff --git a/Ring/Ring/Protocols/ConversationNavigation.swift b/Ring/Ring/Protocols/ConversationNavigation.swift index 22c741bb2217810a8d29720d67d752b1457bda85..f01bda3cc6e6cf6f5469a380c029309aafc48f03 100644 --- a/Ring/Ring/Protocols/ConversationNavigation.swift +++ b/Ring/Ring/Protocols/ConversationNavigation.swift @@ -36,7 +36,7 @@ enum ConversationState: State { case fromCallToConversation(conversation: ConversationViewModel) case needAccountMigration(accountId: String) case accountModeChanged - case openFullScreenPreview(parentView: UIViewController, viewModel: PlayerViewModel?, image: UIImage?, initialFrame: CGRect) + case openFullScreenPreview(parentView: UIViewController, viewModel: PlayerViewModel?, image: UIImage?, initialFrame: CGRect, delegate: PreviewViewControllerDelegate) case replaceCurrentWithConversationFor(participantUri: String) } @@ -75,8 +75,8 @@ extension ConversationNavigation where Self: Coordinator, Self: StateableRespons self.migrateAccount(accountId: accountId) case .accountModeChanged: self.accountModeChanged() - case .openFullScreenPreview(let parentView, let viewModel, let image, let initialFrame): - self.openFullScreenPreview(parentView: parentView, viewModel: viewModel, image: image, initialFrame: initialFrame) + case .openFullScreenPreview(let parentView, let viewModel, let image, let initialFrame, let delegate): + self.openFullScreenPreview(parentView: parentView, viewModel: viewModel, image: image, initialFrame: initialFrame, delegate: delegate) default: break } @@ -105,9 +105,10 @@ extension ConversationNavigation where Self: Coordinator, Self: StateableRespons withStateable: recordFileViewController.viewModel) } - func openFullScreenPreview(parentView: UIViewController, viewModel: PlayerViewModel?, image: UIImage?, initialFrame: CGRect) { + func openFullScreenPreview(parentView: UIViewController, viewModel: PlayerViewModel?, image: UIImage?, initialFrame: CGRect, delegate: PreviewViewControllerDelegate) { if viewModel == nil && image == nil { return } let previewController = PreviewViewController.instantiate(with: self.injectionBag) + previewController.delegate = delegate if let viewModel = viewModel { previewController.viewModel.playerViewModel = viewModel previewController.type = .player diff --git a/Ring/Ring/Resources/Images.xcassets/Contents.json b/Ring/Ring/Resources/Images.xcassets/Contents.json index 37c3608ab673951e0b0c18f4d6a3051b828206b4..18a5a36fd88f995005adb7fc678cde23abe71fd6 100644 --- a/Ring/Ring/Resources/Images.xcassets/Contents.json +++ b/Ring/Ring/Resources/Images.xcassets/Contents.json @@ -1,9 +1,9 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 }, "properties" : { "compression-type" : "lossy" } -} \ No newline at end of file +} diff --git a/Ring/Ring/Resources/Images.xcassets/ic_conversation_remove.imageset/Contents.json b/Ring/Ring/Resources/Images.xcassets/ic_conversation_remove.imageset/Contents.json index e73088e1e7011de24e2bdb5695d2290d5cb2f5f4..07738c2f43a16583abdfbf46100daeaed6719d5b 100644 --- a/Ring/Ring/Resources/Images.xcassets/ic_conversation_remove.imageset/Contents.json +++ b/Ring/Ring/Resources/Images.xcassets/ic_conversation_remove.imageset/Contents.json @@ -1,23 +1,27 @@ { "images" : [ { + "filename" : "trash-can-outline.png", "idiom" : "universal", - "filename" : "delete-2.png", "scale" : "1x" }, { + "filename" : "trash-can-outline-2.png", "idiom" : "universal", - "filename" : "delete.png", "scale" : "2x" }, { + "filename" : "trash-can-outline-3.png", "idiom" : "universal", - "filename" : "delete-1.png", "scale" : "3x" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" } -} \ No newline at end of file +} diff --git a/Ring/Ring/Resources/Images.xcassets/ic_conversation_remove.imageset/delete-1.png b/Ring/Ring/Resources/Images.xcassets/ic_conversation_remove.imageset/delete-1.png deleted file mode 100644 index 04d7a8edcbfb56087078e6509c0e8f39b3ac2461..0000000000000000000000000000000000000000 Binary files a/Ring/Ring/Resources/Images.xcassets/ic_conversation_remove.imageset/delete-1.png and /dev/null differ diff --git a/Ring/Ring/Resources/Images.xcassets/ic_conversation_remove.imageset/delete-2.png b/Ring/Ring/Resources/Images.xcassets/ic_conversation_remove.imageset/delete-2.png deleted file mode 100644 index 8e741fc4248e814b22b74fb58cc2891b3e4ea0da..0000000000000000000000000000000000000000 Binary files a/Ring/Ring/Resources/Images.xcassets/ic_conversation_remove.imageset/delete-2.png and /dev/null differ diff --git a/Ring/Ring/Resources/Images.xcassets/ic_conversation_remove.imageset/delete.png b/Ring/Ring/Resources/Images.xcassets/ic_conversation_remove.imageset/delete.png deleted file mode 100644 index d182d87146a01e56fc1aff7eb7ef4676852001e8..0000000000000000000000000000000000000000 Binary files a/Ring/Ring/Resources/Images.xcassets/ic_conversation_remove.imageset/delete.png and /dev/null differ diff --git a/Ring/Ring/Resources/Images.xcassets/ic_conversation_remove.imageset/trash-can-outline-2.png b/Ring/Ring/Resources/Images.xcassets/ic_conversation_remove.imageset/trash-can-outline-2.png new file mode 100644 index 0000000000000000000000000000000000000000..1a3e09cebd4b5665f22a9792bb403e40986e8d56 Binary files /dev/null and b/Ring/Ring/Resources/Images.xcassets/ic_conversation_remove.imageset/trash-can-outline-2.png differ diff --git a/Ring/Ring/Resources/Images.xcassets/ic_conversation_remove.imageset/trash-can-outline-3.png b/Ring/Ring/Resources/Images.xcassets/ic_conversation_remove.imageset/trash-can-outline-3.png new file mode 100644 index 0000000000000000000000000000000000000000..4dc5af188f782b8018e9aa8d4da19daf5b200415 Binary files /dev/null and b/Ring/Ring/Resources/Images.xcassets/ic_conversation_remove.imageset/trash-can-outline-3.png differ diff --git a/Ring/Ring/Resources/Images.xcassets/ic_conversation_remove.imageset/trash-can-outline.png b/Ring/Ring/Resources/Images.xcassets/ic_conversation_remove.imageset/trash-can-outline.png new file mode 100644 index 0000000000000000000000000000000000000000..6a02c464c273daa50ac11ff3c51d51ca0d56ccf2 Binary files /dev/null and b/Ring/Ring/Resources/Images.xcassets/ic_conversation_remove.imageset/trash-can-outline.png differ diff --git a/Ring/Ring/Resources/Images.xcassets/ic_forward.imageset/Contents.json b/Ring/Ring/Resources/Images.xcassets/ic_forward.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..ac290e18f3c1a0e9a0f9814955aa720ed38ec125 --- /dev/null +++ b/Ring/Ring/Resources/Images.xcassets/ic_forward.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "share-outline.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "share-outline-2.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "share-outline-4.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Ring/Ring/Resources/Images.xcassets/ic_forward.imageset/share-outline-2.png b/Ring/Ring/Resources/Images.xcassets/ic_forward.imageset/share-outline-2.png new file mode 100644 index 0000000000000000000000000000000000000000..de5588ac43f6f007ac0ed86875cf637bfe253a30 Binary files /dev/null and b/Ring/Ring/Resources/Images.xcassets/ic_forward.imageset/share-outline-2.png differ diff --git a/Ring/Ring/Resources/Images.xcassets/ic_forward.imageset/share-outline-4.png b/Ring/Ring/Resources/Images.xcassets/ic_forward.imageset/share-outline-4.png new file mode 100644 index 0000000000000000000000000000000000000000..618f70b96585daa2a263596b9d262b1a9e049bfc Binary files /dev/null and b/Ring/Ring/Resources/Images.xcassets/ic_forward.imageset/share-outline-4.png differ diff --git a/Ring/Ring/Resources/Images.xcassets/ic_forward.imageset/share-outline.png b/Ring/Ring/Resources/Images.xcassets/ic_forward.imageset/share-outline.png new file mode 100644 index 0000000000000000000000000000000000000000..7850257c550347ae850dcdd76efd3fc1daac3362 Binary files /dev/null and b/Ring/Ring/Resources/Images.xcassets/ic_forward.imageset/share-outline.png differ diff --git a/Ring/Ring/Resources/Images.xcassets/ic_save.imageset/Contents.json b/Ring/Ring/Resources/Images.xcassets/ic_save.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..35c0d5881c8bea3f6fb72d5081b4196b002aa8c2 --- /dev/null +++ b/Ring/Ring/Resources/Images.xcassets/ic_save.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "tray-arrow-down.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "tray-arrow-down-2.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "tray-arrow-down-4.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Ring/Ring/Resources/Images.xcassets/ic_save.imageset/tray-arrow-down-2.png b/Ring/Ring/Resources/Images.xcassets/ic_save.imageset/tray-arrow-down-2.png new file mode 100644 index 0000000000000000000000000000000000000000..91ede05ff54b0705ab75972dde0002807dd11942 Binary files /dev/null and b/Ring/Ring/Resources/Images.xcassets/ic_save.imageset/tray-arrow-down-2.png differ diff --git a/Ring/Ring/Resources/Images.xcassets/ic_save.imageset/tray-arrow-down-4.png b/Ring/Ring/Resources/Images.xcassets/ic_save.imageset/tray-arrow-down-4.png new file mode 100644 index 0000000000000000000000000000000000000000..2564dcfee939661f5ab4d6b58bd2473999dc1902 Binary files /dev/null and b/Ring/Ring/Resources/Images.xcassets/ic_save.imageset/tray-arrow-down-4.png differ diff --git a/Ring/Ring/Resources/Images.xcassets/ic_save.imageset/tray-arrow-down.png b/Ring/Ring/Resources/Images.xcassets/ic_save.imageset/tray-arrow-down.png new file mode 100644 index 0000000000000000000000000000000000000000..16f6e969a6fcf77feedeeca48a62a6a1fb52d94b Binary files /dev/null and b/Ring/Ring/Resources/Images.xcassets/ic_save.imageset/tray-arrow-down.png differ diff --git a/Ring/Ring/Resources/Images.xcassets/ic_share.imageset/Contents.json b/Ring/Ring/Resources/Images.xcassets/ic_share.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..c9e7ccc34f749cf81778cb65455153f269e027cf --- /dev/null +++ b/Ring/Ring/Resources/Images.xcassets/ic_share.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "export-variant-3.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "export-variant-4.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "export-variant.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Ring/Ring/Resources/Images.xcassets/ic_share.imageset/export-variant-3.png b/Ring/Ring/Resources/Images.xcassets/ic_share.imageset/export-variant-3.png new file mode 100644 index 0000000000000000000000000000000000000000..16dd4d7800bb414d63df05af1e6dffd5c585273a Binary files /dev/null and b/Ring/Ring/Resources/Images.xcassets/ic_share.imageset/export-variant-3.png differ diff --git a/Ring/Ring/Resources/Images.xcassets/ic_share.imageset/export-variant-4.png b/Ring/Ring/Resources/Images.xcassets/ic_share.imageset/export-variant-4.png new file mode 100644 index 0000000000000000000000000000000000000000..74cc4fafcf3c29ab6cc58f20e771aae97309c9bf Binary files /dev/null and b/Ring/Ring/Resources/Images.xcassets/ic_share.imageset/export-variant-4.png differ diff --git a/Ring/Ring/Resources/Images.xcassets/ic_share.imageset/export-variant.png b/Ring/Ring/Resources/Images.xcassets/ic_share.imageset/export-variant.png new file mode 100644 index 0000000000000000000000000000000000000000..44ab9a3edc40397e1ac9a818bdb47bb6b1157a99 Binary files /dev/null and b/Ring/Ring/Resources/Images.xcassets/ic_share.imageset/export-variant.png differ diff --git a/Ring/Ring/Resources/en.lproj/Localizable.strings b/Ring/Ring/Resources/en.lproj/Localizable.strings index c9d80a5e2ca4fa2a268cd1c77aa6a2d92fb1e7d8..6eaee9da816850a47bf13b81a3b4147bcea81a51 100644 --- a/Ring/Ring/Resources/en.lproj/Localizable.strings +++ b/Ring/Ring/Resources/en.lproj/Localizable.strings @@ -26,6 +26,10 @@ "global.versionName" = "Together"; "global.close" = "Close"; "global.share" = "Share"; +"global.forward" = "Forward"; +"global.save" = "Save"; +"global.resend" = "Resend"; +"global.preview" = "Preview"; // Scan "scan.badQrCode" = "Bad QR code"; @@ -50,6 +54,7 @@ "conversation.messagePlaceholder" = "Write message to "; "conversation.explanationSendingLocationTo" = "You are currently sharing your location with "; "conversation.explanationReceivingLocationFrom" = "You are currently receiving a live location from "; +"conversation.errorSavingImage" = "Failed to save image to galery"; //Invitations "invitations.noInvitations" = "No invitations";