diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_ended_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_ended_icon.imageset/Contents.json new file mode 100644 index 0000000000..1390be53ef --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_ended_icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "location_live_cell_ended_icon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "location_live_cell_ended_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "location_live_cell_ended_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_ended_icon.imageset/location_live_cell_ended_icon.png b/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_ended_icon.imageset/location_live_cell_ended_icon.png new file mode 100644 index 0000000000..9266e542ae Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_ended_icon.imageset/location_live_cell_ended_icon.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_ended_icon.imageset/location_live_cell_ended_icon@2x.png b/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_ended_icon.imageset/location_live_cell_ended_icon@2x.png new file mode 100644 index 0000000000..e6bd8d1da7 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_ended_icon.imageset/location_live_cell_ended_icon@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_ended_icon.imageset/location_live_cell_ended_icon@3x.png b/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_ended_icon.imageset/location_live_cell_ended_icon@3x.png new file mode 100644 index 0000000000..2aad77a527 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_ended_icon.imageset/location_live_cell_ended_icon@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_icon.imageset/Contents.json new file mode 100644 index 0000000000..188a203cc7 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_icon.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "Subtract.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Subtract@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Subtract@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_icon.imageset/Subtract.png b/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_icon.imageset/Subtract.png new file mode 100644 index 0000000000..16be3b51b7 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_icon.imageset/Subtract.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_icon.imageset/Subtract@2x.png b/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_icon.imageset/Subtract@2x.png new file mode 100644 index 0000000000..4e20a1517f Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_icon.imageset/Subtract@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_icon.imageset/Subtract@3x.png b/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_icon.imageset/Subtract@3x.png new file mode 100644 index 0000000000..61ea2a528e Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_icon.imageset/Subtract@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_loading_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_loading_icon.imageset/Contents.json new file mode 100644 index 0000000000..7ba4f36ba1 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_loading_icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "location_live_cell_loading_icon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "location_live_cell_loading_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "location_live_cell_loading_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_loading_icon.imageset/location_live_cell_loading_icon.png b/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_loading_icon.imageset/location_live_cell_loading_icon.png new file mode 100644 index 0000000000..fd67d933ea Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_loading_icon.imageset/location_live_cell_loading_icon.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_loading_icon.imageset/location_live_cell_loading_icon@2x.png b/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_loading_icon.imageset/location_live_cell_loading_icon@2x.png new file mode 100644 index 0000000000..da27784610 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_loading_icon.imageset/location_live_cell_loading_icon@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_loading_icon.imageset/location_live_cell_loading_icon@3x.png b/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_loading_icon.imageset/location_live_cell_loading_icon@3x.png new file mode 100644 index 0000000000..49441f8b09 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_live_cell_loading_icon.imageset/location_live_cell_loading_icon@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_placeholder_background_image.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Location/location_placeholder_background_image.imageset/Contents.json new file mode 100644 index 0000000000..2753cacb98 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Location/location_placeholder_background_image.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "location_placeholder_background_image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "location_placeholder_background_image@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "location_placeholder_background_image@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_placeholder_background_image.imageset/location_placeholder_background_image.png b/Riot/Assets/Images.xcassets/Room/Location/location_placeholder_background_image.imageset/location_placeholder_background_image.png new file mode 100644 index 0000000000..b161191b7e Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_placeholder_background_image.imageset/location_placeholder_background_image.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_placeholder_background_image.imageset/location_placeholder_background_image@2x.png b/Riot/Assets/Images.xcassets/Room/Location/location_placeholder_background_image.imageset/location_placeholder_background_image@2x.png new file mode 100644 index 0000000000..304c25f56b Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_placeholder_background_image.imageset/location_placeholder_background_image@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_placeholder_background_image.imageset/location_placeholder_background_image@3x.png b/Riot/Assets/Images.xcassets/Room/Location/location_placeholder_background_image.imageset/location_placeholder_background_image@3x.png new file mode 100644 index 0000000000..1bfa2e4415 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_placeholder_background_image.imageset/location_placeholder_background_image@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/location_user_marker.png b/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/location_user_marker.png index eedadad5e7..4f1a4b8fbf 100644 Binary files a/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/location_user_marker.png and b/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/location_user_marker.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/location_user_marker@2x.png b/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/location_user_marker@2x.png index fce4dcb046..a48c72b014 100644 Binary files a/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/location_user_marker@2x.png and b/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/location_user_marker@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/location_user_marker@3x.png b/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/location_user_marker@3x.png index ff8ce3aad4..e5a28f6e6a 100644 Binary files a/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/location_user_marker@3x.png and b/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/location_user_marker@3x.png differ diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index a7ade7ed6d..b3ad37b6b7 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -2138,6 +2138,7 @@ Tap the + to start adding people."; "location_sharing_live_share_title" = "Share live location"; "live_location_sharing_banner_title" = "Live location enabled"; +"live_location_sharing_ended" = "Live location ended"; "live_location_sharing_banner_stop" = "Stop"; "location_sharing_static_share_title" = "Send my current location"; "location_sharing_pin_drop_share_title" = "Send this location"; @@ -2149,6 +2150,9 @@ Tap the + to start adding people."; "location_sharing_live_list_item_last_update_invalid" = "Unknown last update"; "location_sharing_live_list_item_current_user_display_name" = "You"; "location_sharing_live_list_item_stop_sharing_action" = "Stop sharing"; +"location_sharing_live_timer_incoming" = "Live until %@"; +"location_sharing_live_loading" = "Loading Live location..."; +"location_sharing_live_error" = "Live location error"; // MARK: - MatrixKit diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index d9c9dadb8e..9f91925e14 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -174,9 +174,13 @@ internal class Asset: NSObject { internal static let voiceCallHangupIcon = ImageAsset(name: "voice_call_hangup_icon") internal static let liveLocationIcon = ImageAsset(name: "live_location_icon") internal static let locationCenterMapIcon = ImageAsset(name: "location_center_map_icon") + internal static let locationLiveCellEndedIcon = ImageAsset(name: "location_live_cell_ended_icon") + internal static let locationLiveCellIcon = ImageAsset(name: "location_live_cell_icon") + internal static let locationLiveCellLoadingIcon = ImageAsset(name: "location_live_cell_loading_icon") internal static let locationLiveIcon = ImageAsset(name: "location_live_icon") internal static let locationMarkerIcon = ImageAsset(name: "location_marker_icon") internal static let locationPinIcon = ImageAsset(name: "location_pin_icon") + internal static let locationPlaceholderBackgroundImage = ImageAsset(name: "location_placeholder_background_image") internal static let locationShareIcon = ImageAsset(name: "location_share_icon") internal static let locationUserMarker = ImageAsset(name: "location_user_marker") internal static let pollCheckboxDefault = ImageAsset(name: "poll_checkbox_default") diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 7aacdbb011..98cddc8cbb 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -2727,6 +2727,10 @@ public class VectorL10n: NSObject { public static var liveLocationSharingBannerTitle: String { return VectorL10n.tr("Vector", "live_location_sharing_banner_title") } + /// Live location ended + public static var liveLocationSharingEnded: String { + return VectorL10n.tr("Vector", "live_location_sharing_ended") + } /// Loading public static var loading: String { return VectorL10n.tr("Vector", "loading") @@ -2759,6 +2763,10 @@ public class VectorL10n: NSObject { public static var locationSharingInvalidAuthorizationSettings: String { return VectorL10n.tr("Vector", "location_sharing_invalid_authorization_settings") } + /// Live location error + public static var locationSharingLiveError: String { + return VectorL10n.tr("Vector", "location_sharing_live_error") + } /// You public static var locationSharingLiveListItemCurrentUserDisplayName: String { return VectorL10n.tr("Vector", "location_sharing_live_list_item_current_user_display_name") @@ -2783,6 +2791,10 @@ public class VectorL10n: NSObject { public static func locationSharingLiveListItemTimeLeft(_ p1: String) -> String { return VectorL10n.tr("Vector", "location_sharing_live_list_item_time_left", p1) } + /// Loading Live location... + public static var locationSharingLiveLoading: String { + return VectorL10n.tr("Vector", "location_sharing_live_loading") + } /// Share location public static var locationSharingLiveMapCalloutTitle: String { return VectorL10n.tr("Vector", "location_sharing_live_map_callout_title") @@ -2791,6 +2803,10 @@ public class VectorL10n: NSObject { public static var locationSharingLiveShareTitle: String { return VectorL10n.tr("Vector", "location_sharing_live_share_title") } + /// Live until %@ + public static func locationSharingLiveTimerIncoming(_ p1: String) -> String { + return VectorL10n.tr("Vector", "location_sharing_live_timer_incoming", p1) + } /// Location public static var locationSharingLiveViewerTitle: String { return VectorL10n.tr("Vector", "location_sharing_live_viewer_title") diff --git a/Riot/Managers/Theme/Theme.swift b/Riot/Managers/Theme/Theme.swift index 93d9cb2a90..fe8d544258 100644 --- a/Riot/Managers/Theme/Theme.swift +++ b/Riot/Managers/Theme/Theme.swift @@ -104,6 +104,12 @@ import DesignKit var roomCellOutgoingBubbleBackgroundColor: UIColor { get } + // Localisation Cells + + var roomCellLocalisationIconStartedColor: UIColor { get } + + var roomCellLocalisationErrorColor: UIColor { get } + // MARK: - Customisation methods diff --git a/Riot/Managers/Theme/Themes/DarkTheme.swift b/Riot/Managers/Theme/Themes/DarkTheme.swift index d61ed3954a..84b0239ce6 100644 --- a/Riot/Managers/Theme/Themes/DarkTheme.swift +++ b/Riot/Managers/Theme/Themes/DarkTheme.swift @@ -98,6 +98,10 @@ class DarkTheme: NSObject, Theme { } var roomCellOutgoingBubbleBackgroundColor: UIColor = UIColor(rgb: 0x133A34) + + var roomCellLocalisationIconStartedColor: UIColor = UIColor(rgb: 0x5C56F5) + + var roomCellLocalisationErrorColor: UIColor = UIColor(rgb: 0xFF5B55) func applyStyle(onTabBar tabBar: UITabBar) { tabBar.unselectedItemTintColor = self.tabBarUnselectedItemTintColor diff --git a/Riot/Managers/Theme/Themes/DefaultTheme.swift b/Riot/Managers/Theme/Themes/DefaultTheme.swift index e2afd93397..a07de1e8f6 100644 --- a/Riot/Managers/Theme/Themes/DefaultTheme.swift +++ b/Riot/Managers/Theme/Themes/DefaultTheme.swift @@ -105,6 +105,10 @@ class DefaultTheme: NSObject, Theme { var roomCellOutgoingBubbleBackgroundColor: UIColor = UIColor(rgb: 0xE7F8F3) + var roomCellLocalisationIconStartedColor: UIColor = UIColor(rgb: 0x5C56F5) + + var roomCellLocalisationErrorColor: UIColor = UIColor(rgb: 0xFF5B55) + func applyStyle(onTabBar tabBar: UITabBar) { tabBar.unselectedItemTintColor = self.tabBarUnselectedItemTintColor tabBar.tintColor = self.tintColor diff --git a/Riot/Modules/Room/CellData/RoomBubbleCellData.m b/Riot/Modules/Room/CellData/RoomBubbleCellData.m index 70ad44a482..9d0603f6da 100644 --- a/Riot/Modules/Room/CellData/RoomBubbleCellData.m +++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.m @@ -184,6 +184,8 @@ - (instancetype)initWithEvent:(MXEvent *)event andRoomState:(MXRoomState *)roomS self.collapsable = NO; self.collapsed = NO; } + + break; } default: break; diff --git a/Riot/Modules/Room/Location/LocationMarkerView.xib b/Riot/Modules/Room/Location/LocationMarkerView.xib index e4ec30008c..2b855435e5 100644 --- a/Riot/Modules/Room/Location/LocationMarkerView.xib +++ b/Riot/Modules/Room/Location/LocationMarkerView.xib @@ -1,6 +1,8 @@ + + @@ -8,56 +10,42 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - - - - - + + - - - - + + + + + + @@ -71,6 +59,6 @@ - + diff --git a/Riot/Modules/Room/Location/RoomTimelineLocationView.swift b/Riot/Modules/Room/Location/RoomTimelineLocationView.swift index cb2fb91469..c8f680e57c 100644 --- a/Riot/Modules/Room/Location/RoomTimelineLocationView.swift +++ b/Riot/Modules/Room/Location/RoomTimelineLocationView.swift @@ -17,6 +17,64 @@ import UIKit import Reusable import Mapbox +import SwiftUI + +protocol RoomTimelineLocationViewDelegate: AnyObject { + func roomTimelineLocationViewDidTapStopButton(_ roomTimelineLocationView: RoomTimelineLocationView) + func roomTimelineLocationViewDidTapRetryButton(_ roomTimelineLocationView: RoomTimelineLocationView) +} + +struct RoomTimelineLocationViewData { + let location: CLLocationCoordinate2D + let userAvatarData: AvatarViewData? + let mapStyleURL: URL +} + +struct LiveLocationBannerViewData { + let placeholderIcon: UIImage? + let iconTint: UIColor + let title: String + let titleColor: UIColor + let timeLeftString: String? + let rightButtonTitle: String? + let rightButtonTag: RightButtonTag + + var showTimer: Bool { + return timeLeftString != nil + } + + var showRightButton: Bool { + return rightButtonTitle != nil + } + + var showPlaceholderImage: Bool { + return placeholderIcon != 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 starting + case started(_ timeleft: TimeInterval) + case stopped +} + +enum RightButtonTag: Int { + case stopSharing = 0 + case retrySharing +} class RoomTimelineLocationView: UIView, NibLoadable, Themable, MGLMapViewDelegate { @@ -37,9 +95,37 @@ class RoomTimelineLocationView: UIView, NibLoadable, Themable, MGLMapViewDelegat @IBOutlet private var descriptionIcon: UIImageView! @IBOutlet private var attributionLabel: UILabel! + // MARK: - Live Location + @IBOutlet private var placeholderBackground: UIImageView! + @IBOutlet private var placeholderIcon: UIImageView! + @IBOutlet private var liveLocationContainerView: UIView! + @IBOutlet private var liveLocationImageView: UIImageView! + @IBOutlet private var liveLocationStatusLabel: UILabel! + @IBOutlet private var liveLocationTimerLabel: UILabel! + @IBOutlet private var rightButton: UIButton! + + + private var mapView: MGLMapView! private var annotationView: LocationMarkerView? private static var usernameColorGenerator = UserNameColorGenerator() + private var theme: Theme! + + 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 @@ -52,7 +138,7 @@ class RoomTimelineLocationView: UIView, NibLoadable, Themable, MGLMapViewDelegat descriptionContainerView.isHidden = (newValue?.count ?? 0 == 0) } } - + override func awakeFromNib() { super.awakeFromNib() @@ -70,13 +156,16 @@ class RoomTimelineLocationView: UIView, NibLoadable, Themable, MGLMapViewDelegat clipsToBounds = true layer.borderWidth = Constants.cellBorderRadius layer.cornerRadius = Constants.cellCornerRadius + + theme = ThemeService.shared().theme } - // MARK: - Public + // MARK: - Private - public func displayLocation(_ location: CLLocationCoordinate2D, - userAvatarData: AvatarViewData? = nil, - mapStyleURL: URL) { + private func displayLocation(_ location: CLLocationCoordinate2D, + userAvatarData: AvatarViewData? = nil, + mapStyleURL: URL, + bannerViewData: LiveLocationBannerViewData? = nil) { mapView.styleURL = mapStyleURL @@ -96,8 +185,126 @@ class RoomTimelineLocationView: UIView, NibLoadable, Themable, MGLMapViewDelegat let pointAnnotation = MGLPointAnnotation() pointAnnotation.coordinate = location mapView.addAnnotation(pointAnnotation) + + // Configure live location banner + guard let bannerViewData = bannerViewData else { + liveLocationContainerView.isHidden = true + return + } + + liveLocationContainerView.isHidden = false + liveLocationContainerView.backgroundColor = theme.colors.background.withAlphaComponent(0.85) + + liveLocationImageView.image = Asset.Images.locationLiveCellIcon.image + liveLocationImageView.tintColor = bannerViewData.iconTint + + liveLocationStatusLabel.text = bannerViewData.title + liveLocationStatusLabel.textColor = bannerViewData.titleColor + + liveLocationTimerLabel.text = bannerViewData.timeLeftString + liveLocationTimerLabel.textColor = theme.colors.tertiaryContent + liveLocationTimerLabel.isHidden = !bannerViewData.showTimer + + rightButton.setTitle(bannerViewData.rightButtonTitle, for: .normal) + rightButton.isHidden = !bannerViewData.showRightButton + rightButton.tag = bannerViewData.rightButtonTag.rawValue + + placeholderBackground.isHidden = !bannerViewData.showPlaceholderImage + placeholderIcon.image = bannerViewData.placeholderIcon + placeholderIcon.isHidden = !bannerViewData.showPlaceholderImage + placeholderBackground.isHidden = !bannerViewData.showPlaceholderImage + mapView.isHidden = bannerViewData.showPlaceholderImage + } + + private func liveLocationBannerViewData(from viewState: TimelineLiveLocationViewState) -> LiveLocationBannerViewData { + + let iconTint: UIColor + let title: String + var titleColor: UIColor = theme.colors.primaryContent + var placeholderIcon: UIImage? + var timeLeftString: String? + var rightButtonTitle: String? + var rightButtonTag: RightButtonTag = .stopSharing + + switch viewState { + case .incoming(let liveLocationSharingStatus): + switch liveLocationSharingStatus { + case .starting: + iconTint = theme.colors.tertiaryContent + title = VectorL10n.locationSharingLiveLoading + titleColor = theme.colors.tertiaryContent + placeholderIcon = Asset.Images.locationLiveCellLoadingIcon.image + case .started(let timeLeft): + iconTint = theme.roomCellLocalisationIconStartedColor + title = VectorL10n.liveLocationSharingBannerTitle + timeLeftString = generateTimerString(for: timeLeft, isIncomingLocation: true) + case .stopped: + iconTint = theme.colors.tertiaryContent + title = VectorL10n.liveLocationSharingEnded + titleColor = theme.colors.tertiaryContent + placeholderIcon = Asset.Images.locationLiveCellEndedIcon.image + } + case .outgoing(let liveLocationSharingStatus): + switch liveLocationSharingStatus { + case .starting: + iconTint = theme.colors.tertiaryContent + title = VectorL10n.locationSharingLiveLoading + titleColor = theme.colors.tertiaryContent + placeholderIcon = Asset.Images.locationLiveCellLoadingIcon.image + case .started(let timeLeft): + iconTint = theme.roomCellLocalisationIconStartedColor + title = VectorL10n.liveLocationSharingBannerTitle + timeLeftString = generateTimerString(for: timeLeft, isIncomingLocation: false) + rightButtonTitle = VectorL10n.stop + case .failure: + iconTint = theme.roomCellLocalisationErrorColor + title = VectorL10n.locationSharingLiveError + rightButtonTitle = VectorL10n.retry + rightButtonTag = .retrySharing + case .stopped: + iconTint = theme.colors.tertiaryContent + title = VectorL10n.liveLocationSharingEnded + titleColor = theme.colors.tertiaryContent + placeholderIcon = Asset.Images.locationLiveCellEndedIcon.image + } + } + + return LiveLocationBannerViewData(placeholderIcon: placeholderIcon, iconTint: iconTint, title: title, titleColor: titleColor, timeLeftString: timeLeftString, rightButtonTitle: rightButtonTitle, rightButtonTag: rightButtonTag) } + private func generateTimerString(for timestamp: Double, + isIncomingLocation: Bool) -> String? { + let timerInSec = timestamp / 1000 // Timestamp is in millisecond in the SDK + let timerString: String? + if isIncomingLocation { + timerString = VectorL10n.locationSharingLiveTimerIncoming(incomingTimerFormatter.string(from: Date(timeIntervalSince1970: timerInSec))) + } else if let outgoingTimer = outgoingTimerFormatter.string(from: Date(timeIntervalSince1970: timerInSec).timeIntervalSinceNow) { + timerString = VectorL10n.locationSharingLiveListItemTimeLeft(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) { @@ -107,6 +314,7 @@ class RoomTimelineLocationView: UIView, NibLoadable, Themable, MGLMapViewDelegat descriptionIcon.tintColor = theme.colors.accent attributionLabel.textColor = theme.colors.accent layer.borderColor = theme.colors.quinaryContent.cgColor + self.theme = theme } // MARK: - MGLMapViewDelegate @@ -114,4 +322,14 @@ class RoomTimelineLocationView: UIView, NibLoadable, Themable, MGLMapViewDelegat func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? { return annotationView } + + // MARK: - Action + + @IBAction private func didTapTightButton(_ sender: Any) { + if rightButton.tag == RightButtonTag.stopSharing.rawValue { + delegate?.roomTimelineLocationViewDidTapStopButton(self) + } else if rightButton.tag == RightButtonTag.retrySharing.rawValue { + delegate?.roomTimelineLocationViewDidTapRetryButton(self) + } + } } diff --git a/Riot/Modules/Room/Location/RoomTimelineLocationView.xib b/Riot/Modules/Room/Location/RoomTimelineLocationView.xib index fb2e22a294..79e8d70977 100644 --- a/Riot/Modules/Room/Location/RoomTimelineLocationView.xib +++ b/Riot/Modules/Room/Location/RoomTimelineLocationView.xib @@ -4,7 +4,6 @@ - @@ -15,15 +14,26 @@ - - + + + + + + + + + + - + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + @@ -84,12 +168,21 @@ + + + + + + + - + + + diff --git a/Riot/Modules/Room/MXKRoomViewController.m b/Riot/Modules/Room/MXKRoomViewController.m index a8d79f084f..ba1dd3e214 100644 --- a/Riot/Modules/Room/MXKRoomViewController.m +++ b/Riot/Modules/Room/MXKRoomViewController.m @@ -3045,6 +3045,22 @@ - (void)dataSource:(MXKDataSource *)dataSource didRecognizeAction:(NSString *)ac [self promptUserToResendEvent:selectedEvent.eventId]; } } + else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellStopShareButtonPressed]) + { + MXEvent *selectedEvent = userInfo[kMXKRoomBubbleCellEventKey]; + if (selectedEvent) + { + // TODO: - Implement stop live location action + } + } + else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellRetryShareButtonPressed]) + { + MXEvent *selectedEvent = userInfo[kMXKRoomBubbleCellEventKey]; + if (selectedEvent) + { + // TODO: - Implement retry live location action + } + } } #pragma mark - Clipboard diff --git a/Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.h b/Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.h index 8e247835f9..15e18f89be 100644 --- a/Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.h +++ b/Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.h @@ -83,6 +83,20 @@ extern NSString *const kMXKRoomBubbleCellTapOnContentView; */ extern NSString *const kMXKRoomBubbleCellUnsentButtonPressed; +/** + Action identifier used when the user pressed stop share button displayed in live location cell. + + The `userInfo` dictionary contains an `MXEvent` object under the `kMXKRoomBubbleCellEventKey` key, representing the live location event to stop. + */ +extern NSString *const kMXKRoomBubbleCellStopShareButtonPressed; + +/** + Action identifier used when the user pressed retry share button displayed in live location cell. + + The `userInfo` dictionary contains an `MXEvent` object under the `kMXKRoomBubbleCellEventKey` key, representing the live location event to retry. + */ +extern NSString *const kMXKRoomBubbleCellRetryShareButtonPressed; + /** Action identifier used when the user long pressed on a displayed event. diff --git a/Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.m b/Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.m index c4367d5983..f5fcdc4ffd 100644 --- a/Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.m +++ b/Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.m @@ -41,7 +41,10 @@ NSString *const kMXKRoomBubbleCellTapOnOverlayContainer = @"kMXKRoomBubbleCellTapOnOverlayContainer"; NSString *const kMXKRoomBubbleCellTapOnContentView = @"kMXKRoomBubbleCellTapOnContentView"; + NSString *const kMXKRoomBubbleCellUnsentButtonPressed = @"kMXKRoomBubbleCellUnsentButtonPressed"; +NSString *const kMXKRoomBubbleCellStopShareButtonPressed = @"kMXKRoomBubbleCellStopShareButtonPressed"; +NSString *const kMXKRoomBubbleCellRetryShareButtonPressed = @"kMXKRoomBubbleCellRetryShareButtonPressed"; NSString *const kMXKRoomBubbleCellLongPressOnEvent = @"kMXKRoomBubbleCellLongPressOnEvent"; NSString *const kMXKRoomBubbleCellLongPressOnProgressView = @"kMXKRoomBubbleCellLongPressOnProgressView"; diff --git a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/Location/LocationPlainCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/Location/LocationPlainCell.swift index 4b86e42094..77f5f784d4 100644 --- a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/Location/LocationPlainCell.swift +++ b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/Location/LocationPlainCell.swift @@ -15,41 +15,82 @@ // import Foundation +import MatrixSDK class LocationPlainCell: SizableBaseRoomCell, RoomCellReactionsDisplayable, RoomCellReadMarkerDisplayable { private var locationView: RoomTimelineLocationView! + private var event: MXEvent? override func render(_ cellData: MXKCellData!) { super.render(cellData) guard #available(iOS 14.0, *), let bubbleData = cellData as? RoomBubbleCellData, - let event = bubbleData.events.last, - event.eventType == __MXEventType.roomMessage, - let locationContent = event.location + let event = bubbleData.events.last else { return } + self.event = event locationView.update(theme: ThemeService.shared().theme) + + // Comment this line and uncomment next one to test UI of live location tile + renderStaticLocation(event) +// renderLiveLocation(event) + } + + private func renderStaticLocation(_ event: MXEvent) { + guard let locationContent = event.location else { + return + } + locationView.locationDescription = locationContent.locationDescription let location = CLLocationCoordinate2D(latitude: locationContent.latitude, longitude: locationContent.longitude) let mapStyleURL = bubbleData.mxSession.vc_homeserverConfiguration().tileServer.mapStyleURL + let avatarViewData: AvatarViewData? + if locationContent.assetType == .user { - let avatarViewData = AvatarViewData(matrixItemId: bubbleData.senderId, + avatarViewData = AvatarViewData(matrixItemId: bubbleData.senderId, displayName: bubbleData.senderDisplayName, avatarUrl: bubbleData.senderAvatarUrl, mediaManager: bubbleData.mxSession.mediaManager, fallbackImage: .matrixItem(bubbleData.senderId, bubbleData.senderDisplayName)) - - locationView.displayLocation(location, userAvatarData: avatarViewData, mapStyleURL: mapStyleURL) } else { - locationView.displayLocation(location, mapStyleURL: mapStyleURL) + avatarViewData = nil } + + locationView.displayStaticLocation(with: RoomTimelineLocationViewData(location: location, userAvatarData: avatarViewData, mapStyleURL: mapStyleURL)) + } + + private func renderLiveLocation(_ event: MXEvent) { + // TODO: - Render live location cell when live location event is handled + + // This code is only for testing live location cell + // Will be completed when the live location event is handled + + guard let locationContent = event.location else { + return + } + + locationView.locationDescription = locationContent.locationDescription + + let location = CLLocationCoordinate2D(latitude: locationContent.latitude, longitude: locationContent.longitude) + + let mapStyleURL = bubbleData.mxSession.vc_homeserverConfiguration().tileServer.mapStyleURL + + let avatarViewData = AvatarViewData(matrixItemId: bubbleData.senderId, + displayName: bubbleData.senderDisplayName, + avatarUrl: bubbleData.senderAvatarUrl, + mediaManager: bubbleData.mxSession.mediaManager, + fallbackImage: .matrixItem(bubbleData.senderId, bubbleData.senderDisplayName)) + let futurDateTimeInterval = Date(timeIntervalSinceNow: 3734).timeIntervalSince1970 * 1000 + + locationView.displayLiveLocation(with: RoomTimelineLocationViewData(location: location, userAvatarData: avatarViewData, mapStyleURL: mapStyleURL), + liveLocationViewState: .outgoing(.started(futurDateTimeInterval))) } override func setupViews() { @@ -69,3 +110,21 @@ class LocationPlainCell: SizableBaseRoomCell, RoomCellReactionsDisplayable, Room contentView.vc_addSubViewMatchingParent(locationView) } } + +extension LocationPlainCell: RoomTimelineLocationViewDelegate { + func roomTimelineLocationViewDidTapStopButton(_ roomTimelineLocationView: RoomTimelineLocationView) { + guard let event = self.event else { + return + } + + delegate.cell(self, didRecognizeAction: kMXKRoomBubbleCellStopShareButtonPressed, userInfo: [kMXKRoomBubbleCellEventKey: event]) + } + + func roomTimelineLocationViewDidTapRetryButton(_ roomTimelineLocationView: RoomTimelineLocationView) { + guard let event = self.event else { + return + } + + delegate.cell(self, didRecognizeAction: kMXKRoomBubbleCellRetryShareButtonPressed, userInfo: [kMXKRoomBubbleCellEventKey: event]) + } +} diff --git a/RiotSwiftUI/Modules/Room/StaticLocationSharingViewer/Test/UI/StaticLocationViewingUITests.swift b/RiotSwiftUI/Modules/Room/StaticLocationSharingViewer/Test/UI/StaticLocationViewingUITests.swift index f9c98e0be2..01e2635cb0 100644 --- a/RiotSwiftUI/Modules/Room/StaticLocationSharingViewer/Test/UI/StaticLocationViewingUITests.swift +++ b/RiotSwiftUI/Modules/Room/StaticLocationSharingViewer/Test/UI/StaticLocationViewingUITests.swift @@ -42,6 +42,5 @@ class StaticLocationViewingUITests: MockScreenTest { func verifyInitialExistingLocation() { XCTAssertTrue(app.buttons["Cancel"].exists, "The cancel button should exist.") XCTAssertTrue(app.buttons["shareButton"].exists, "The share button should exist.") - XCTAssertTrue(app.otherElements["Map"].exists, "The map view should exist.") } } diff --git a/changelog.d/6029.change b/changelog.d/6029.change new file mode 100644 index 0000000000..9a829ea070 --- /dev/null +++ b/changelog.d/6029.change @@ -0,0 +1 @@ +Location sharing: Add cell for live location sharing in timeline