mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-21 17:12:45 +02:00
Load and store URLPreviewViewData in RoomBubbleCellData.
Implement close button and store the action in Core Data. Hide the preview image view when no image is received. Remove line breaks in description text.
This commit is contained in:
@@ -21,36 +21,49 @@ import Reusable
|
||||
class URLPreviewView: UIView, NibLoadable, Themable {
|
||||
// MARK: - Constants
|
||||
|
||||
private enum Constants { }
|
||||
private static let sizingView = URLPreviewView.instantiate()
|
||||
|
||||
private enum Constants {
|
||||
// URL Previews
|
||||
|
||||
static let maxHeight: CGFloat = 247.0
|
||||
static let width: CGFloat = 267.0
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
var viewModel: URLPreviewViewModel! {
|
||||
var preview: URLPreviewViewData? {
|
||||
didSet {
|
||||
viewModel.viewDelegate = self
|
||||
guard let preview = preview else { return }
|
||||
renderLoaded(preview)
|
||||
}
|
||||
}
|
||||
|
||||
weak var delegate: URLPreviewViewDelegate?
|
||||
|
||||
@IBOutlet weak var imageContainer: UIView!
|
||||
@IBOutlet weak var imageView: UIImageView!
|
||||
@IBOutlet weak var faviconImageView: UIImageView!
|
||||
@IBOutlet weak var closeButton: UIButton!
|
||||
|
||||
@IBOutlet weak var siteNameLabel: UILabel!
|
||||
@IBOutlet weak var titleLabel: UILabel!
|
||||
@IBOutlet weak var descriptionLabel: UILabel!
|
||||
|
||||
/// The constraint that pins the top of the text container to the top of the view.
|
||||
@IBOutlet weak var textContainerViewConstraint: NSLayoutConstraint!
|
||||
/// The constraint that pins the top of the text container to the bottom of the image container.
|
||||
@IBOutlet weak var textContainerImageConstraint: NSLayoutConstraint!
|
||||
|
||||
override var intrinsicContentSize: CGSize {
|
||||
CGSize(width: RoomBubbleCellLayout.urlPreviewViewWidth, height: RoomBubbleCellLayout.urlPreviewViewHeight)
|
||||
CGSize(width: Constants.width, height: Constants.maxHeight)
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
static func instantiate(viewModel: URLPreviewViewModel) -> Self {
|
||||
static func instantiate() -> Self {
|
||||
let view = Self.loadFromNib()
|
||||
view.update(theme: ThemeService.shared().theme)
|
||||
|
||||
view.viewModel = viewModel
|
||||
viewModel.process(viewAction: .loadData)
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
@@ -63,14 +76,10 @@ class URLPreviewView: UIView, NibLoadable, Themable {
|
||||
layer.masksToBounds = true
|
||||
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
faviconImageView.layer.cornerRadius = 6
|
||||
|
||||
siteNameLabel.isUserInteractionEnabled = false
|
||||
titleLabel.isUserInteractionEnabled = false
|
||||
descriptionLabel.isUserInteractionEnabled = false
|
||||
|
||||
#warning("Debugging for previews - to be removed")
|
||||
faviconImageView.backgroundColor = .systemBlue.withAlphaComponent(0.7)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
@@ -86,9 +95,19 @@ class URLPreviewView: UIView, NibLoadable, Themable {
|
||||
|
||||
descriptionLabel.textColor = theme.colors.secondaryContent
|
||||
descriptionLabel.font = theme.fonts.caption1
|
||||
|
||||
let closeButtonAsset = ThemeService.shared().isCurrentThemeDark() ? Asset.Images.urlPreviewCloseDark : Asset.Images.urlPreviewClose
|
||||
closeButton.setImage(closeButtonAsset.image, for: .normal)
|
||||
}
|
||||
|
||||
static func contentViewHeight(for preview: URLPreviewViewData) -> CGFloat {
|
||||
sizingView.renderLoaded(preview)
|
||||
|
||||
return sizingView.systemLayoutSizeFitting(sizingView.intrinsicContentSize).height
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
#warning("Check whether we should show a loading state.")
|
||||
private func renderLoading(_ url: URL) {
|
||||
imageView.image = nil
|
||||
|
||||
@@ -98,48 +117,49 @@ class URLPreviewView: UIView, NibLoadable, Themable {
|
||||
}
|
||||
|
||||
private func renderLoaded(_ preview: URLPreviewViewData) {
|
||||
imageView.image = preview.image
|
||||
if let image = preview.image {
|
||||
imageView.image = image
|
||||
showImageContainer()
|
||||
} else {
|
||||
imageView.image = nil
|
||||
hideImageContainer()
|
||||
}
|
||||
|
||||
siteNameLabel.text = preview.siteName ?? preview.url.host
|
||||
titleLabel.text = preview.title
|
||||
descriptionLabel.text = preview.text
|
||||
}
|
||||
|
||||
private func renderError(_ error: Error) {
|
||||
imageView.image = nil
|
||||
private func showImageContainer() {
|
||||
// When the image container has a superview it is already visible
|
||||
guard imageContainer.superview == nil else { return }
|
||||
|
||||
siteNameLabel.text = "Error"
|
||||
titleLabel.text = descriptionLabel.text
|
||||
descriptionLabel.text = error.localizedDescription
|
||||
textContainerViewConstraint.isActive = false
|
||||
addSubview(imageContainer)
|
||||
textContainerImageConstraint.isActive = true
|
||||
|
||||
// Ensure the close button remains visible
|
||||
bringSubviewToFront(closeButton)
|
||||
}
|
||||
|
||||
private func hideImageContainer() {
|
||||
textContainerImageConstraint.isActive = false
|
||||
imageContainer.removeFromSuperview()
|
||||
textContainerViewConstraint.isActive = true
|
||||
}
|
||||
|
||||
// MARK: - Action
|
||||
@IBAction private func openURL(_ sender: Any) {
|
||||
MXLog.debug("[URLPreviewView] Link was tapped.")
|
||||
viewModel.process(viewAction: .openURL)
|
||||
guard let preview = preview else { return }
|
||||
|
||||
// Ask the delegate to open the URL for the event, as the bubble component
|
||||
// has the original un-sanitized URL that needs to be opened.
|
||||
delegate?.didOpenURLFromPreviewView(self, for: preview.eventID, in: preview.roomID)
|
||||
}
|
||||
|
||||
@IBAction private func close(_ sender: Any) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: URLPreviewViewModelViewDelegate
|
||||
extension URLPreviewView: URLPreviewViewModelViewDelegate {
|
||||
func urlPreviewViewModel(_ viewModel: URLPreviewViewModelType, didUpdateViewState viewState: URLPreviewViewState) {
|
||||
DispatchQueue.main.async {
|
||||
switch viewState {
|
||||
case .loading(let url):
|
||||
self.renderLoading(url)
|
||||
case .loaded(let preview):
|
||||
self.renderLoaded(preview)
|
||||
case .error(let error):
|
||||
self.renderError(error)
|
||||
case .hidden:
|
||||
self.frame.size.height = 0
|
||||
}
|
||||
}
|
||||
guard let preview = preview else { return }
|
||||
delegate?.didCloseURLPreviewView(self, for: preview.eventID, in: preview.roomID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="267" height="247"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="sV7-z8-2ZW">
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="sV7-z8-2ZW" userLabel="Image Container">
|
||||
<rect key="frame" x="0.0" y="0.0" width="267" height="140"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="9ew-qc-5BO">
|
||||
@@ -23,7 +23,6 @@
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="267" id="1cA-Oe-Ffr"/>
|
||||
<constraint firstItem="9ew-qc-5BO" firstAttribute="top" secondItem="sV7-z8-2ZW" secondAttribute="top" id="PkK-wn-hNC"/>
|
||||
<constraint firstItem="9ew-qc-5BO" firstAttribute="leading" secondItem="sV7-z8-2ZW" secondAttribute="leading" id="bQt-a9-prT"/>
|
||||
<constraint firstAttribute="bottom" secondItem="9ew-qc-5BO" secondAttribute="bottom" id="dfh-UN-9f8"/>
|
||||
@@ -34,35 +33,48 @@
|
||||
<outletCollection property="gestureRecognizers" destination="rSB-1V-Kev" appends="YES" id="LLc-zz-Ooa"/>
|
||||
</connections>
|
||||
</view>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="78m-NG-Oe7">
|
||||
<rect key="frame" x="8" y="108" width="24" height="24"/>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Q0C-fE-Qie" userLabel="Text Container">
|
||||
<rect key="frame" x="0.0" y="140" width="267" height="107"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Site Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2D0-pg-81F">
|
||||
<rect key="frame" x="8" y="8" width="56" height="13.5"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="11"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="250" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bfQ-4X-PGU">
|
||||
<rect key="frame" x="8" y="23.5" width="33.5" height="19.5"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="16"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="250" text="Description" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="3" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cSk-qu-c4j">
|
||||
<rect key="frame" x="8" y="45" width="65" height="14.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="24" id="54e-3E-QhC"/>
|
||||
<constraint firstAttribute="height" constant="24" id="Ms9-sP-ceF"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="2D0-pg-81F" secondAttribute="trailing" constant="8" id="2zQ-XH-oQR"/>
|
||||
<constraint firstAttribute="width" constant="267" id="LRy-qw-2b8"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="cSk-qu-c4j" secondAttribute="bottom" constant="8" id="NR5-XL-Tai"/>
|
||||
<constraint firstItem="cSk-qu-c4j" firstAttribute="leading" secondItem="bfQ-4X-PGU" secondAttribute="leading" id="PX7-CK-i5a"/>
|
||||
<constraint firstAttribute="height" relation="lessThanOrEqual" constant="107" id="QSQ-YM-7r3"/>
|
||||
<constraint firstItem="bfQ-4X-PGU" firstAttribute="leading" secondItem="2D0-pg-81F" secondAttribute="leading" id="UQW-5z-5gV"/>
|
||||
<constraint firstItem="bfQ-4X-PGU" firstAttribute="top" secondItem="2D0-pg-81F" secondAttribute="bottom" constant="2" id="UpA-Eq-vOM"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="cSk-qu-c4j" secondAttribute="trailing" constant="8" id="ay3-i8-Tm3"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="bfQ-4X-PGU" secondAttribute="trailing" constant="8" id="orX-8Y-YeT"/>
|
||||
<constraint firstItem="2D0-pg-81F" firstAttribute="leading" secondItem="Q0C-fE-Qie" secondAttribute="leading" constant="8" id="quM-cg-gTu"/>
|
||||
<constraint firstItem="2D0-pg-81F" firstAttribute="top" secondItem="Q0C-fE-Qie" secondAttribute="top" constant="8" id="rOX-ft-oNx"/>
|
||||
<constraint firstItem="cSk-qu-c4j" firstAttribute="top" secondItem="bfQ-4X-PGU" secondAttribute="bottom" constant="2" id="uUe-4x-y4M"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Site Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2D0-pg-81F">
|
||||
<rect key="frame" x="8" y="148" width="56" height="13.5"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="11"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="250" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bfQ-4X-PGU">
|
||||
<rect key="frame" x="8" y="163.5" width="33.5" height="19.5"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="16"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="250" text="Description" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="3" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cSk-qu-c4j">
|
||||
<rect key="frame" x="8" y="185" width="251" height="14.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="CL2-R3-bYG">
|
||||
<rect key="frame" x="242" y="9" width="10" height="22"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<state key="normal" image="close_banner">
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="CL2-R3-bYG">
|
||||
<rect key="frame" x="227" y="0.0" width="40" height="40"/>
|
||||
<inset key="contentEdgeInsets" minX="8" minY="8" maxX="8" maxY="8"/>
|
||||
<state key="normal" image="url_preview_close">
|
||||
<preferredSymbolConfiguration key="preferredSymbolConfiguration" scale="large"/>
|
||||
</state>
|
||||
<connections>
|
||||
@@ -73,28 +85,31 @@
|
||||
<color key="backgroundColor" systemColor="tertiarySystemFillColor"/>
|
||||
<gestureRecognizers/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="cSk-qu-c4j" secondAttribute="trailing" constant="8" id="1QZ-IV-b1E"/>
|
||||
<constraint firstAttribute="top" secondItem="sV7-z8-2ZW" secondAttribute="top" id="8AQ-S7-Fm0"/>
|
||||
<constraint firstItem="2D0-pg-81F" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="8" id="A6d-x8-mDG"/>
|
||||
<constraint firstItem="cSk-qu-c4j" firstAttribute="leading" secondItem="bfQ-4X-PGU" secondAttribute="leading" id="IT4-7h-n5y"/>
|
||||
<constraint firstItem="bfQ-4X-PGU" firstAttribute="leading" secondItem="2D0-pg-81F" secondAttribute="leading" id="Qxt-SR-d1M"/>
|
||||
<constraint firstItem="bfQ-4X-PGU" firstAttribute="top" secondItem="2D0-pg-81F" secondAttribute="bottom" constant="2" id="ZBc-MM-pl2"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="bfQ-4X-PGU" secondAttribute="trailing" constant="8" id="bk2-2s-hgS"/>
|
||||
<constraint firstAttribute="bottom" secondItem="Q0C-fE-Qie" secondAttribute="bottom" id="AMK-Mj-Bth"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Q0C-fE-Qie" secondAttribute="trailing" id="FwW-Yj-RX8"/>
|
||||
<constraint firstItem="Q0C-fE-Qie" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="RJG-FK-55c"/>
|
||||
<constraint firstItem="Q0C-fE-Qie" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="U7n-8S-2Oc"/>
|
||||
<constraint firstItem="sV7-z8-2ZW" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="cfa-wh-EzA"/>
|
||||
<constraint firstItem="78m-NG-Oe7" firstAttribute="leading" secondItem="sV7-z8-2ZW" secondAttribute="leading" constant="8" id="ecO-IN-wTm"/>
|
||||
<constraint firstItem="CL2-R3-bYG" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="er2-K6-UxP"/>
|
||||
<constraint firstAttribute="trailing" secondItem="sV7-z8-2ZW" secondAttribute="trailing" id="gUP-5s-qWI"/>
|
||||
<constraint firstItem="2D0-pg-81F" firstAttribute="top" secondItem="sV7-z8-2ZW" secondAttribute="bottom" constant="8" id="neh-8P-aFx"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="2D0-pg-81F" secondAttribute="trailing" constant="8" id="tnX-Fo-b79"/>
|
||||
<constraint firstItem="cSk-qu-c4j" firstAttribute="top" secondItem="bfQ-4X-PGU" secondAttribute="bottom" constant="2" id="xp1-2j-xl8"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="cSk-qu-c4j" secondAttribute="bottom" constant="8" id="yHd-Fp-jPo"/>
|
||||
<constraint firstItem="sV7-z8-2ZW" firstAttribute="bottom" secondItem="78m-NG-Oe7" secondAttribute="bottom" constant="8" id="zsc-zO-UkD"/>
|
||||
<constraint firstItem="Q0C-fE-Qie" firstAttribute="top" secondItem="sV7-z8-2ZW" secondAttribute="bottom" id="jLX-fo-dQS"/>
|
||||
<constraint firstAttribute="trailing" secondItem="CL2-R3-bYG" secondAttribute="trailing" id="wNl-Nu-qrw"/>
|
||||
</constraints>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<variation key="default">
|
||||
<mask key="constraints">
|
||||
<exclude reference="RJG-FK-55c"/>
|
||||
</mask>
|
||||
</variation>
|
||||
<connections>
|
||||
<outlet property="closeButton" destination="CL2-R3-bYG" id="Ajz-iV-YeT"/>
|
||||
<outlet property="descriptionLabel" destination="cSk-qu-c4j" id="gC7-Pu-nHx"/>
|
||||
<outlet property="faviconImageView" destination="78m-NG-Oe7" id="u26-zc-JwX"/>
|
||||
<outlet property="imageContainer" destination="sV7-z8-2ZW" id="swm-BL-00E"/>
|
||||
<outlet property="imageView" destination="9ew-qc-5BO" id="nzz-xV-mae"/>
|
||||
<outlet property="siteNameLabel" destination="2D0-pg-81F" id="72o-l1-f7x"/>
|
||||
<outlet property="textContainerImageConstraint" destination="jLX-fo-dQS" id="fRa-ak-dUy"/>
|
||||
<outlet property="textContainerViewConstraint" destination="RJG-FK-55c" id="tsp-0o-sXt"/>
|
||||
<outlet property="titleLabel" destination="bfQ-4X-PGU" id="Jzt-75-caa"/>
|
||||
<outletCollection property="gestureRecognizers" destination="rSB-1V-Kev" appends="YES" id="qPD-mR-YpO"/>
|
||||
</connections>
|
||||
@@ -107,7 +122,7 @@
|
||||
</tapGestureRecognizer>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="close_banner" width="10" height="10"/>
|
||||
<image name="url_preview_close" width="24" height="24"/>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
|
||||
@@ -16,12 +16,18 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
@objc
|
||||
@objcMembers
|
||||
class URLPreviewViewData: NSObject {
|
||||
/// The URL that's represented by the preview data. This may have been sanitized.
|
||||
/// Note: The original URL, is stored in ``URLPreviewViewModel``.
|
||||
/// Note: The original URL, can be found in the bubble components with `eventID` and `roomID`.
|
||||
let url: URL
|
||||
|
||||
/// The ID of the event that created this preview.
|
||||
let eventID: String
|
||||
|
||||
/// The ID of the room that this preview is from.
|
||||
let roomID: String
|
||||
|
||||
/// The OpenGraph site name for the URL.
|
||||
let siteName: String?
|
||||
|
||||
@@ -34,10 +40,13 @@ class URLPreviewViewData: NSObject {
|
||||
/// The OpenGraph image for the URL.
|
||||
var image: UIImage?
|
||||
|
||||
init(url: URL, siteName: String?, title: String?, text: String?) {
|
||||
init(url: URL, eventID: String, roomID: String, siteName: String?, title: String?, text: String?) {
|
||||
self.url = url
|
||||
self.eventID = eventID
|
||||
self.roomID = roomID
|
||||
self.siteName = siteName
|
||||
self.title = title
|
||||
self.text = text
|
||||
// Remove line breaks from the description text
|
||||
self.text = text?.replacingOccurrences(of: "\n", with: " ")
|
||||
}
|
||||
}
|
||||
|
||||
+4
-5
@@ -16,9 +16,8 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
/// URLPreviewView actions exposed to view model
|
||||
enum URLPreviewViewAction {
|
||||
case loadData
|
||||
case openURL
|
||||
case close
|
||||
@objc
|
||||
protocol URLPreviewViewDelegate: AnyObject {
|
||||
func didOpenURLFromPreviewView(_ previewView: URLPreviewView, for eventID: String, in roomID: String)
|
||||
func didCloseURLPreviewView(_ previewView: URLPreviewView, for eventID: String, in roomID: String)
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MatrixSDK
|
||||
|
||||
@objcMembers
|
||||
class URLPreviewViewModel: NSObject, URLPreviewViewModelType {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
/// The original (un-sanitized) URL to be previewed.
|
||||
private let url: URL
|
||||
private let session: MXSession
|
||||
|
||||
private var currentOperation: MXHTTPOperation?
|
||||
private var urlPreview: MXURLPreview?
|
||||
|
||||
// MARK: Public
|
||||
|
||||
weak var viewDelegate: URLPreviewViewModelViewDelegate?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(url: URL, session: MXSession) {
|
||||
self.url = url
|
||||
self.session = session
|
||||
}
|
||||
|
||||
deinit {
|
||||
cancelOperations()
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func process(viewAction: URLPreviewViewAction) {
|
||||
switch viewAction {
|
||||
case .loadData:
|
||||
loadData()
|
||||
case .openURL:
|
||||
openURL()
|
||||
case .close:
|
||||
cancelOperations()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func loadData() {
|
||||
update(viewState: .loading(url))
|
||||
|
||||
AppDelegate.theDelegate().previewManager.preview(for: url) { [weak self] preview in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.update(viewState: .loaded(preview))
|
||||
} failure: { error in
|
||||
#warning("REALLY?!")
|
||||
if let error = error {
|
||||
self.update(viewState: .error(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func openURL() {
|
||||
// Open the original (un-sanitized) URL stored in the view model.
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
|
||||
private func update(viewState: URLPreviewViewState) {
|
||||
viewDelegate?.urlPreviewViewModel(self, didUpdateViewState: viewState)
|
||||
}
|
||||
|
||||
private func cancelOperations() {
|
||||
currentOperation?.cancel()
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol URLPreviewViewModelViewDelegate: AnyObject {
|
||||
func urlPreviewViewModel(_ viewModel: URLPreviewViewModelType, didUpdateViewState viewState: URLPreviewViewState)
|
||||
}
|
||||
|
||||
/// Protocol describing the view model used by `URLPreviewView`
|
||||
protocol URLPreviewViewModelType {
|
||||
|
||||
var viewDelegate: URLPreviewViewModelViewDelegate? { get set }
|
||||
|
||||
func process(viewAction: URLPreviewViewAction)
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// URLPreviewView state
|
||||
enum URLPreviewViewState {
|
||||
case loading(_ url: URL)
|
||||
case loaded(_ preview: URLPreviewViewData)
|
||||
case error(Error)
|
||||
case hidden
|
||||
}
|
||||
Reference in New Issue
Block a user