mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-20 16:42:44 +02:00
6029: Refacto how to handle all state of the live location cell
This commit is contained in:
@@ -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"/>
|
||||
|
||||
Reference in New Issue
Block a user