6029: Refacto how to handle all state of the live location cell

This commit is contained in:
MaximeE
2022-04-22 18:04:19 +02:00
parent b1ad2419ad
commit 0633d01abe
24 changed files with 368 additions and 73 deletions
@@ -19,26 +19,60 @@ import Reusable
import Mapbox
import SwiftUI
struct LiveLocationParameter {
let bannerImage: UIImage
let bannerTitle: String
let timer: String?
let shouldShowStopButton: Bool
let isLive: Bool
protocol RoomTimelineLocationViewDelegate: AnyObject {
func didTapStopButton()
func didTapRetryButton()
}
enum LiveLocationState {
case incomingLive(String?)
case outgoingLive(String?)
struct RoomTimelineLocationViewData {
let location: CLLocationCoordinate2D
let userAvatarData: AvatarViewData?
let mapStyleURL: URL
}
struct LiveLocationBannerViewData {
let placeholderImage: UIImage?
let iconTint: UIColor
let title: String
let titleColor: UIColor
let timeLeftString: String?
let rightButtonTitle: String?
let rightButtonTag: RightButtonTag
func values() -> LiveLocationParameter {
switch self {
case .incomingLive(let timerString):
return LiveLocationParameter(bannerImage: Asset.Images.locationLiveIcon.image, bannerTitle: VectorL10n.liveLocationSharingBannerTitle, timer: timerString, shouldShowStopButton: false, isLive: true)
case .outgoingLive(let timerString):
return LiveLocationParameter(bannerImage: Asset.Images.locationLiveIcon.image, bannerTitle: VectorL10n.liveLocationSharingBannerTitle, timer: timerString, shouldShowStopButton: true, isLive: true)
}
var showTimer: Bool {
return timeLeftString != nil
}
var showRightButton: Bool {
return rightButtonTitle != nil
}
var showPlaceholderImage: Bool {
return placeholderImage != nil
}
}
enum TimelineLiveLocationViewState {
case incoming(_ status: IncomingLiveLocationSharingStatus) // live location started by other users
case outgoing(_ status: OutgoingLiveLocationSharingStatus) // live location started from current user
}
enum OutgoingLiveLocationSharingStatus {
case starting
case started(_ timeleft: TimeInterval)
case failure
case stopped
}
enum IncomingLiveLocationSharingStatus {
case started(_ timeleft: TimeInterval)
case stopped
}
enum RightButtonTag: Int {
case stopSharing = 0
case retrySharing
}
class RoomTimelineLocationView: UIView, NibLoadable, Themable, MGLMapViewDelegate {
@@ -61,11 +95,12 @@ class RoomTimelineLocationView: UIView, NibLoadable, Themable, MGLMapViewDelegat
@IBOutlet private var attributionLabel: UILabel!
// MARK: - Live Location
@IBOutlet private var placeholderImage: UIImageView!
@IBOutlet private var liveLocationContainerView: UIView!
@IBOutlet private var liveLocationImageView: UIImageView!
@IBOutlet private var liveLocationStatusLabel: UILabel!
@IBOutlet private var liveLocationTimerLabel: UILabel!
@IBOutlet private var stopSharingButton: UIButton!
@IBOutlet private var rightButton: UIButton!
@@ -73,6 +108,22 @@ class RoomTimelineLocationView: UIView, NibLoadable, Themable, MGLMapViewDelegat
private var annotationView: LocationMarkerView?
private static var usernameColorGenerator = UserNameColorGenerator()
private lazy var incomingTimerFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm"
return dateFormatter
}()
private lazy var outgoingTimerFormatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.zeroFormattingBehavior = .dropAll
formatter.allowedUnits = [.hour, .minute, .second]
formatter.unitsStyle = .brief
return formatter
}()
weak var delegate: RoomTimelineLocationViewDelegate?
// MARK: Public
var locationDescription: String? {
@@ -84,7 +135,7 @@ class RoomTimelineLocationView: UIView, NibLoadable, Themable, MGLMapViewDelegat
descriptionContainerView.isHidden = (newValue?.count ?? 0 == 0)
}
}
override func awakeFromNib() {
super.awakeFromNib()
@@ -104,12 +155,12 @@ class RoomTimelineLocationView: UIView, NibLoadable, Themable, MGLMapViewDelegat
layer.cornerRadius = Constants.cellCornerRadius
}
// MARK: - Public
// MARK: - Private
public func displayLocation(_ location: CLLocationCoordinate2D,
userAvatarData: AvatarViewData? = nil,
mapStyleURL: URL,
liveLocationState: LiveLocationState? = nil) {
private func displayLocation(_ location: CLLocationCoordinate2D,
userAvatarData: AvatarViewData? = nil,
mapStyleURL: URL,
bannerViewData: LiveLocationBannerViewData? = nil) {
mapView.styleURL = mapStyleURL
@@ -131,23 +182,116 @@ class RoomTimelineLocationView: UIView, NibLoadable, Themable, MGLMapViewDelegat
mapView.addAnnotation(pointAnnotation)
// Configure live location banner
guard let liveLocationParameters = liveLocationState?.values() else {
guard let bannerViewData = bannerViewData else {
liveLocationContainerView.isHidden = true
return
}
liveLocationContainerView.isHidden = false
liveLocationImageView.image = liveLocationParameters.bannerImage
liveLocationStatusLabel.text = liveLocationParameters.bannerTitle
if let timerString = liveLocationParameters.timer {
liveLocationTimerLabel.isHidden = false
liveLocationTimerLabel.text = timerString
} else {
liveLocationTimerLabel.isHidden = true
}
stopSharingButton.isHidden = !liveLocationParameters.shouldShowStopButton
liveLocationImageView.image = Asset.Images.locationLiveCellIcon.image
liveLocationImageView.tintColor = bannerViewData.iconTint
liveLocationStatusLabel.text = bannerViewData.title
liveLocationStatusLabel.textColor = bannerViewData.titleColor
liveLocationTimerLabel.text = bannerViewData.timeLeftString
liveLocationTimerLabel.isHidden = !bannerViewData.showTimer
rightButton.setTitle(bannerViewData.rightButtonTitle, for: .normal)
rightButton.isHidden = !bannerViewData.showRightButton
rightButton.tag = bannerViewData.rightButtonTag.rawValue
placeholderImage.image = bannerViewData.placeholderImage
placeholderImage.isHidden = !bannerViewData.showPlaceholderImage
mapView.isHidden = bannerViewData.showPlaceholderImage
}
private func liveLocationBannerViewData(from viewState: TimelineLiveLocationViewState) -> LiveLocationBannerViewData {
let theme = ThemeService.shared().theme
let iconTint: UIColor
let title: String
var titleColor: UIColor = theme.roomCellLocalisationTextColor
var placeholderImage: UIImage?
var timeLeftString: String?
var rightButtonTitle: String?
var rightButtonTag: RightButtonTag = .stopSharing
switch viewState {
case .incoming(let liveLocationSharingStatus):
switch liveLocationSharingStatus {
case .started(let timeLeft):
iconTint = theme.roomCellLocalisationStartedColor
title = VectorL10n.liveLocationSharingBannerTitle
timeLeftString = generateTimerString(for: timeLeft, isIncomingLocation: true)
case .stopped:
iconTint = theme.roomCellLocalisationEndedColor
title = VectorL10n.liveLocationSharingEnded
titleColor = theme.roomCellLocalisationEndedColor
placeholderImage = Asset.Images.locationLiveCellEndedImage.image
}
case .outgoing(let liveLocationSharingStatus):
switch liveLocationSharingStatus {
case .starting:
iconTint = theme.roomCellLocalisationEndedColor
title = VectorL10n.locationSharingLiveLoading
titleColor = theme.roomCellLocalisationEndedColor
placeholderImage = Asset.Images.locationLiveCellLoadingImage.image
case .started(let timeLeft):
iconTint = theme.roomCellLocalisationStartedColor
title = VectorL10n.liveLocationSharingBannerTitle
timeLeftString = generateTimerString(for: timeLeft, isIncomingLocation: false)
rightButtonTitle = VectorL10n.stop
case .failure:
iconTint = theme.roomCellLocalisationErrorColor
title = VectorL10n.locationSharingLiveError
titleColor = theme.roomCellLocalisationEndedColor
rightButtonTitle = VectorL10n.retry
rightButtonTag = .retrySharing
case .stopped:
iconTint = theme.roomCellLocalisationEndedColor
title = VectorL10n.liveLocationSharingEnded
titleColor = theme.roomCellLocalisationEndedColor
placeholderImage = Asset.Images.locationLiveCellEndedImage.image
}
}
return LiveLocationBannerViewData(placeholderImage: placeholderImage, iconTint: iconTint, title: title, titleColor: titleColor, timeLeftString: timeLeftString, rightButtonTitle: rightButtonTitle, rightButtonTag: rightButtonTag)
}
private func generateTimerString(for timestamp: Double,
isIncomingLocation: Bool) -> String? {
let timerString: String?
if isIncomingLocation {
timerString = VectorL10n.locationSharingLiveTimerIncoming(incomingTimerFormatter.string(from: Date(timeIntervalSince1970: timestamp)))
} else if let outgoingTimer = outgoingTimerFormatter.string(from: Date(timeIntervalSince1970: timestamp).timeIntervalSinceNow) {
timerString = VectorL10n.locationSharingLiveTimerOutgoing(outgoingTimer)
} else {
timerString = nil
}
return timerString
}
// MARK: - Public
public func displayStaticLocation(with viewData: RoomTimelineLocationViewData) {
displayLocation(viewData.location,
userAvatarData: viewData.userAvatarData,
mapStyleURL: viewData.mapStyleURL,
bannerViewData: nil)
}
public func displayLiveLocation(with viewData: RoomTimelineLocationViewData, liveLocationViewState: TimelineLiveLocationViewState) {
let bannerViewData = liveLocationBannerViewData(from: liveLocationViewState)
displayLocation(viewData.location,
userAvatarData: viewData.userAvatarData,
mapStyleURL: viewData.mapStyleURL,
bannerViewData: bannerViewData)
}
// MARK: - Themable
func update(theme: Theme) {
@@ -167,7 +311,11 @@ class RoomTimelineLocationView: UIView, NibLoadable, Themable, MGLMapViewDelegat
// MARK: - Action
@IBAction private func stopSharingAction(_ sender: Any) {
// TODO: - Stop sharing action
@IBAction private func didTapTightButton(_ sender: Any) {
if rightButton.tag == RightButtonTag.stopSharing.rawValue {
delegate?.didTapStopButton()
} else if rightButton.tag == RightButtonTag.retrySharing.rawValue {
delegate?.didTapRetryButton()
}
}
}
@@ -4,7 +4,6 @@
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
<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>
@@ -15,6 +14,9 @@
<rect key="frame" x="0.0" y="0.0" width="395" height="250"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="vuV-BV-bbx">
<rect key="frame" x="0.0" y="0.0" width="395" height="202"/>
</imageView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="0D1-Km-vTu">
<rect key="frame" x="0.0" y="182" width="395" height="68"/>
<subviews>
@@ -41,7 +43,7 @@
<view alpha="0.84999999999999998" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Hpx-8P-nCb" userLabel="Live Location Container View">
<rect key="frame" x="0.0" y="20" width="395" height="48"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="location_live_icon" translatesAutoresizingMaskIntoConstraints="NO" id="pgy-lK-ehN">
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="location_live_cell_icon" translatesAutoresizingMaskIntoConstraints="NO" id="pgy-lK-ehN">
<rect key="frame" x="8" y="8" width="32" height="32"/>
<constraints>
<constraint firstAttribute="width" constant="32" id="BxH-3l-2Gp"/>
@@ -77,7 +79,7 @@
<color key="titleColor" red="1" green="0.35686274509999999" blue="0.33333333329999998" alpha="1" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="stopSharingAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="Mma-oK-Zbp"/>
<action selector="didTapTightButton:" destination="iN0-l3-epB" eventType="touchUpInside" id="bFp-df-OEs"/>
</connections>
</button>
</subviews>
@@ -129,11 +131,14 @@
</subviews>
</stackView>
</subviews>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="Hpx-8P-nCb" firstAttribute="top" secondItem="vuV-BV-bbx" secondAttribute="bottom" id="CIR-rY-ESf"/>
<constraint firstAttribute="trailing" secondItem="vuV-BV-bbx" secondAttribute="trailing" id="Gck-1z-j8f"/>
<constraint firstItem="vuV-BV-bbx" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="NhI-xD-L7V"/>
<constraint firstAttribute="trailing" secondItem="0D1-Km-vTu" secondAttribute="trailing" id="QHD-xv-nfX"/>
<constraint firstAttribute="bottom" secondItem="0D1-Km-vTu" secondAttribute="bottom" id="ea5-xx-V3s"/>
<constraint firstItem="vuV-BV-bbx" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="tJb-jf-9mX"/>
<constraint firstItem="0D1-Km-vTu" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="yJw-DU-ien"/>
</constraints>
<nil key="simulatedTopBarMetrics"/>
@@ -148,13 +153,14 @@
<outlet property="liveLocationImageView" destination="pgy-lK-ehN" id="1Th-PJ-G0p"/>
<outlet property="liveLocationStatusLabel" destination="X9R-EV-RDT" id="HJr-cE-icv"/>
<outlet property="liveLocationTimerLabel" destination="U75-c5-got" id="RkO-6a-ABU"/>
<outlet property="stopSharingButton" destination="k5H-UE-ygH" id="9JN-eB-pDt"/>
<outlet property="placeholderImage" destination="vuV-BV-bbx" id="QYU-6R-SI8"/>
<outlet property="rightButton" destination="k5H-UE-ygH" id="nz0-Eb-SRp"/>
</connections>
<point key="canvasLocation" x="165.94202898550725" y="-101.11607142857143"/>
</view>
</objects>
<resources>
<image name="location_live_icon" width="40" height="40"/>
<image name="location_live_cell_icon" width="32" height="32"/>
<image name="location_marker_icon" width="24" height="24"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>