diff --git a/DesignKit/Source/Colors.swift b/DesignKit/Source/Colors.swift index aaa5a936c4..d7c885e59c 100644 --- a/DesignKit/Source/Colors.swift +++ b/DesignKit/Source/Colors.swift @@ -67,5 +67,4 @@ public protocol Colors { /// - Names in chat timeline /// - Avatars default states that include first name letter var namesAndAvatars: [ColorType] { get } - } diff --git a/DesignKit/Source/ColorsUIkit.swift b/DesignKit/Source/ColorsUIkit.swift index 47c6b38a93..3add385c36 100644 --- a/DesignKit/Source/ColorsUIkit.swift +++ b/DesignKit/Source/ColorsUIkit.swift @@ -47,7 +47,7 @@ import UIKit public let background: UIColor public let namesAndAvatars: [UIColor] - + init(values: ColorValues) { accent = values.accent alert = values.alert diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_live_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Location/location_live_icon.imageset/Contents.json new file mode 100644 index 0000000000..0a20c899a3 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Location/location_live_icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "location_live_icon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "location_live_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "location_live_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_live_icon.imageset/location_live_icon.png b/Riot/Assets/Images.xcassets/Room/Location/location_live_icon.imageset/location_live_icon.png new file mode 100644 index 0000000000..80a96f7b18 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_live_icon.imageset/location_live_icon.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_live_icon.imageset/location_live_icon@2x.png b/Riot/Assets/Images.xcassets/Room/Location/location_live_icon.imageset/location_live_icon@2x.png new file mode 100644 index 0000000000..f9b203ee18 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_live_icon.imageset/location_live_icon@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_live_icon.imageset/location_live_icon@3x.png b/Riot/Assets/Images.xcassets/Room/Location/location_live_icon.imageset/location_live_icon@3x.png new file mode 100644 index 0000000000..68565c599c Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_live_icon.imageset/location_live_icon@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_pin_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Location/location_pin_icon.imageset/Contents.json new file mode 100644 index 0000000000..0f1b5bed3e --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Location/location_pin_icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "location_pin_icon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "location_pin_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "location_pin_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_pin_icon.imageset/location_pin_icon.png b/Riot/Assets/Images.xcassets/Room/Location/location_pin_icon.imageset/location_pin_icon.png new file mode 100644 index 0000000000..697ac06ee1 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_pin_icon.imageset/location_pin_icon.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_pin_icon.imageset/location_pin_icon@2x.png b/Riot/Assets/Images.xcassets/Room/Location/location_pin_icon.imageset/location_pin_icon@2x.png new file mode 100644 index 0000000000..025ece33f7 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_pin_icon.imageset/location_pin_icon@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_pin_icon.imageset/location_pin_icon@3x.png b/Riot/Assets/Images.xcassets/Room/Location/location_pin_icon.imageset/location_pin_icon@3x.png new file mode 100644 index 0000000000..98927ec855 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_pin_icon.imageset/location_pin_icon@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/Contents.json index 175219374a..86a003473b 100644 --- a/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/Contents.json +++ b/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/Contents.json @@ -19,5 +19,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" } } diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 6b94b9fc0a..bef071e4a3 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -2068,8 +2068,6 @@ Tap the + to start adding people."; "location_sharing_close_action" = "Close"; -"location_sharing_share_action" = "Share"; - "location_sharing_post_failure_title" = "We couldn’t send your location"; "location_sharing_post_failure_subtitle" = "%@ could not send your location. Please try again later."; @@ -2096,9 +2094,11 @@ Tap the + to start adding people."; // MARK: Live location sharing +"location_sharing_live_share_title" = "Share live location"; "live_location_sharing_banner_title" = "Live location enabled"; "live_location_sharing_banner_stop" = "Stop"; - +"location_sharing_static_share_title" = "Send my current location"; +"location_sharing_pin_drop_share_title" = "Send this location"; // MARK: - MatrixKit diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index 3398cc3b5d..0863cd0424 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -173,7 +173,9 @@ internal class Asset: NSObject { internal static let voiceCallHangonIcon = ImageAsset(name: "voice_call_hangon_icon") internal static let voiceCallHangupIcon = ImageAsset(name: "voice_call_hangup_icon") internal static let liveLocationIcon = ImageAsset(name: "live_location_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 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 23461179bc..e79e1dd109 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -2751,6 +2751,10 @@ public class VectorL10n: NSObject { public static var locationSharingInvalidAuthorizationSettings: String { return VectorL10n.tr("Vector", "location_sharing_invalid_authorization_settings") } + /// Share live location + public static var locationSharingLiveShareTitle: String { + return VectorL10n.tr("Vector", "location_sharing_live_share_title") + } /// %@ could not load the map. Please try again later. public static func locationSharingLoadingMapErrorTitle(_ p1: String) -> String { return VectorL10n.tr("Vector", "location_sharing_loading_map_error_title", p1) @@ -2771,6 +2775,10 @@ public class VectorL10n: NSObject { public static var locationSharingOpenOpenStreetMaps: String { return VectorL10n.tr("Vector", "location_sharing_open_open_street_maps") } + /// Send this location + public static var locationSharingPinDropShareTitle: String { + return VectorL10n.tr("Vector", "location_sharing_pin_drop_share_title") + } /// %@ could not send your location. Please try again later. public static func locationSharingPostFailureSubtitle(_ p1: String) -> String { return VectorL10n.tr("Vector", "location_sharing_post_failure_subtitle", p1) @@ -2787,9 +2795,9 @@ public class VectorL10n: NSObject { public static var locationSharingSettingsToggleTitle: String { return VectorL10n.tr("Vector", "location_sharing_settings_toggle_title") } - /// Share - public static var locationSharingShareAction: String { - return VectorL10n.tr("Vector", "location_sharing_share_action") + /// Send my current location + public static var locationSharingStaticShareTitle: String { + return VectorL10n.tr("Vector", "location_sharing_static_share_title") } /// Location public static var locationSharingTitle: String { diff --git a/Riot/Modules/Room/Location/LocationMarkerView.swift b/Riot/Modules/Room/Location/LocationMarkerView.swift index caf101a1ba..3eb46427ee 100644 --- a/Riot/Modules/Room/Location/LocationMarkerView.swift +++ b/Riot/Modules/Room/Location/LocationMarkerView.swift @@ -20,6 +20,7 @@ import Mapbox class LocationMarkerView: MGLAnnotationView, NibLoadable { + @IBOutlet private var backgroundImageView: UIImageView! @IBOutlet private var avatarView: UserAvatarView! override func awakeFromNib() { @@ -27,7 +28,9 @@ class LocationMarkerView: MGLAnnotationView, NibLoadable { translatesAutoresizingMaskIntoConstraints = false } - func setAvatarData(_ avatarData: AvatarViewDataProtocol) { + func setAvatarData(_ avatarData: AvatarViewDataProtocol, avatarBackgroundColor: UIColor) { + backgroundImageView.image = Asset.Images.locationUserMarker.image + backgroundImageView.tintColor = avatarBackgroundColor avatarView.fill(with: avatarData) } } diff --git a/Riot/Modules/Room/Location/LocationMarkerView.xib b/Riot/Modules/Room/Location/LocationMarkerView.xib index 837db55030..ba9cacb271 100644 --- a/Riot/Modules/Room/Location/LocationMarkerView.xib +++ b/Riot/Modules/Room/Location/LocationMarkerView.xib @@ -1,9 +1,7 @@ - - + - - + @@ -65,6 +63,7 @@ + diff --git a/Riot/Modules/Room/Location/RoomTimelineLocationView.swift b/Riot/Modules/Room/Location/RoomTimelineLocationView.swift index 5ffb88e154..cb2fb91469 100644 --- a/Riot/Modules/Room/Location/RoomTimelineLocationView.swift +++ b/Riot/Modules/Room/Location/RoomTimelineLocationView.swift @@ -39,6 +39,7 @@ class RoomTimelineLocationView: UIView, NibLoadable, Themable, MGLMapViewDelegat private var mapView: MGLMapView! private var annotationView: LocationMarkerView? + private static var usernameColorGenerator = UserNameColorGenerator() // MARK: Public @@ -82,7 +83,8 @@ class RoomTimelineLocationView: UIView, NibLoadable, Themable, MGLMapViewDelegat annotationView = LocationMarkerView.loadFromNib() if let userAvatarData = userAvatarData { - annotationView?.setAvatarData(userAvatarData) + let avatarBackgroundColor = Self.usernameColorGenerator.color(from: userAvatarData.matrixItemId) + annotationView?.setAvatarData(userAvatarData, avatarBackgroundColor: avatarBackgroundColor) } if let annotations = mapView.annotations { @@ -99,6 +101,7 @@ class RoomTimelineLocationView: UIView, NibLoadable, Themable, MGLMapViewDelegat // MARK: - Themable func update(theme: Theme) { + Self.usernameColorGenerator.update(theme: theme) descriptionLabel.textColor = theme.colors.primaryContent descriptionLabel.font = theme.fonts.footnote descriptionIcon.tintColor = theme.colors.accent diff --git a/Riot/Modules/Threads/ThreadList/Views/Cell/ThreadTableViewCell.swift b/Riot/Modules/Threads/ThreadList/Views/Cell/ThreadTableViewCell.swift index 48a7f8cf9d..b99eec0c35 100644 --- a/Riot/Modules/Threads/ThreadList/Views/Cell/ThreadTableViewCell.swift +++ b/Riot/Modules/Threads/ThreadList/Views/Cell/ThreadTableViewCell.swift @@ -94,8 +94,7 @@ extension ThreadTableViewCell: Themable { func update(theme: Theme) { self.theme = theme - Self.usernameColorGenerator.defaultColor = theme.colors.primaryContent - Self.usernameColorGenerator.userNameColors = theme.colors.namesAndAvatars + Self.usernameColorGenerator.update(theme: theme) updateRootMessageSenderColor() rootMessageAvatarView.backgroundColor = .clear if let attributedText = rootMessageContentLabel.attributedText { diff --git a/Riot/Utils/UserNameColorGenerator.swift b/Riot/Utils/UserNameColorGenerator.swift index 3e5c2adcd6..9e34361f85 100644 --- a/Riot/Utils/UserNameColorGenerator.swift +++ b/Riot/Utils/UserNameColorGenerator.swift @@ -48,3 +48,12 @@ final class UserNameColorGenerator: NSObject { return self.userNameColors[senderNameColorIndex] } } + +// MARK: - Themable +extension UserNameColorGenerator: Themable { + + func update(theme: Theme) { + self.defaultColor = theme.colors.primaryContent + self.userNameColors = theme.colors.namesAndAvatars + } +} diff --git a/RiotSwiftUI/Modules/Common/Avatar/View/AvatarImage.swift b/RiotSwiftUI/Modules/Common/Avatar/View/AvatarImage.swift index 5bed53bd5e..6d0d444dc9 100644 --- a/RiotSwiftUI/Modules/Common/Avatar/View/AvatarImage.swift +++ b/RiotSwiftUI/Modules/Common/Avatar/View/AvatarImage.swift @@ -42,7 +42,7 @@ struct AvatarImage: View { .resizable() } } - .frame(width: CGFloat(size.rawValue), height: CGFloat(size.rawValue)) + .frame(maxWidth: CGFloat(size.rawValue), maxHeight: CGFloat(size.rawValue)) .clipShape(Circle()) .onAppear { viewModel.inject(dependencies: dependencies) @@ -69,6 +69,19 @@ extension AvatarImage { } } +@available(iOS 14.0, *) +extension AvatarImage { + func border(color: Color) -> some View { + modifier(BorderModifier(color: color, borderWidth: 3, shape: Circle())) + } + + /// Use display name color as border color by default + func border() -> some View { + let borderColor = theme.userColor(for: matrixItemId) + return self.border(color: borderColor) + } +} + @available(iOS 14.0, *) struct AvatarImage_Previews: PreviewProvider { static let mxContentUri = "fakeUri" diff --git a/RiotSwiftUI/Modules/Common/Avatar/ViewModel/AvatarViewModel.swift b/RiotSwiftUI/Modules/Common/Avatar/ViewModel/AvatarViewModel.swift index 2b49dc2897..00d410f806 100644 --- a/RiotSwiftUI/Modules/Common/Avatar/ViewModel/AvatarViewModel.swift +++ b/RiotSwiftUI/Modules/Common/Avatar/ViewModel/AvatarViewModel.swift @@ -52,7 +52,7 @@ class AvatarViewModel: InjectableObject, ObservableObject { return } - avatarService.avatarImage(mxContentUri: mxContentUri, avatarSize: avatarSize) + avatarService.avatarImage(mxContentUri: mxContentUri, avatarSize: avatarSize) .sink { completion in guard case let .failure(error) = completion else { return } UILog.error("[AvatarService] Failed to retrieve avatar: \(error)") diff --git a/RiotSwiftUI/Modules/Common/Theme/ThemeNamesColorsExtension.swift b/RiotSwiftUI/Modules/Common/Theme/ThemeUsersColorsExtension.swift similarity index 89% rename from RiotSwiftUI/Modules/Common/Theme/ThemeNamesColorsExtension.swift rename to RiotSwiftUI/Modules/Common/Theme/ThemeUsersColorsExtension.swift index ae52e2ec71..dc1d93f0ce 100644 --- a/RiotSwiftUI/Modules/Common/Theme/ThemeNamesColorsExtension.swift +++ b/RiotSwiftUI/Modules/Common/Theme/ThemeUsersColorsExtension.swift @@ -20,10 +20,10 @@ import SwiftUI @available(iOS 14.0, *) extension ThemeSwiftUI { - /// Get the stable display name color based on userId. + /// Get the stable display user color based on userId. /// - Parameter userId: The user id used to hash. /// - Returns: The SwiftUI color for the associated userId. - func displayNameColor(for userId: String) -> Color { + func userColor(for userId: String) -> Color { let senderNameColorIndex = Int(userId.vc_hashCode % Int32(colors.namesAndAvatars.count)) return colors.namesAndAvatars[senderNameColorIndex] } diff --git a/RiotSwiftUI/Modules/Common/Util/BorderModifier.swift b/RiotSwiftUI/Modules/Common/Util/BorderModifier.swift new file mode 100644 index 0000000000..e6da415b42 --- /dev/null +++ b/RiotSwiftUI/Modules/Common/Util/BorderModifier.swift @@ -0,0 +1,37 @@ +// +// Copyright 2022 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 SwiftUI + +@available(iOS 14.0, *) +struct BorderModifier: ViewModifier { + + var color: Color + var borderWidth: CGFloat + var shape: Shape + + func body(content: Content) -> some View { + content + .overlay(shape.stroke(color, lineWidth: borderWidth)) + } +} + +@available(iOS 14.0, *) +extension View { + func shapedBorder(color: Color, borderWidth: CGFloat, shape: Shape) -> some View { + modifier(BorderModifier(color: color, borderWidth: borderWidth, shape: shape)) + } +} diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/Coordinator/LocationSharingCoordinator.swift b/RiotSwiftUI/Modules/Room/LocationSharing/Coordinator/LocationSharingCoordinator.swift index ef7c29daec..85adbc669b 100644 --- a/RiotSwiftUI/Modules/Room/LocationSharing/Coordinator/LocationSharingCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/LocationSharing/Coordinator/LocationSharingCoordinator.swift @@ -49,7 +49,8 @@ final class LocationSharingCoordinator: Coordinator, Presentable { let viewModel = LocationSharingViewModel(mapStyleURL: BuildSettings.tileServerMapStyleURL, avatarData: parameters.avatarData, - location: parameters.location) + location: parameters.location, + isLiveLocationSharingEnabled: BuildSettings.liveLocationSharingEnabled) let view = LocationSharingView(context: viewModel.context) .addDependency(AvatarService.instantiate(mediaManager: parameters.mediaManager)) diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingModels.swift b/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingModels.swift index cb9843a469..f06d80562c 100644 --- a/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingModels.swift +++ b/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingModels.swift @@ -54,11 +54,17 @@ struct LocationSharingViewState: BindableState { /// Map annotation to focus on var highlightedAnnotation: UserLocationAnnotation? + /// Indicates whether the user has moved around the map to drop a pin somewhere other than their current location + var isPinDropSharing: Bool = false + var showLoadingIndicator: Bool = false /// True to indicate to show and follow current user location var showsUserLocation: Bool = false + /// Used to hide live location sharing features until is finished + var isLiveLocationSharingEnabled: Bool = false + var shareButtonVisible: Bool { return self.displayExistingLocation == false } diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingScreenState.swift b/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingScreenState.swift index 208b7c2da2..0c9b218533 100644 --- a/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingScreenState.swift +++ b/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingScreenState.swift @@ -36,8 +36,9 @@ enum MockLocationSharingScreenState: MockScreenState, CaseIterable { let mapStyleURL = URL(string: "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx")! let viewModel = LocationSharingViewModel(mapStyleURL: mapStyleURL, - avatarData: AvatarInput(mxContentUri: "", matrixItemId: "", displayName: "Alice"), - location: location) + avatarData: AvatarInput(mxContentUri: "", matrixItemId: "alice:matrix.org", displayName: "Alice"), + location: location, + isLiveLocationSharingEnabled: true) return ([viewModel], AnyView(LocationSharingView(context: viewModel.context) .addDependency(MockAvatarService.example))) diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingViewModel.swift b/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingViewModel.swift index 807c5605c6..bccd953913 100644 --- a/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingViewModel.swift +++ b/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingViewModel.swift @@ -35,7 +35,7 @@ class LocationSharingViewModel: LocationSharingViewModelType, LocationSharingVie // MARK: - Setup - init(mapStyleURL: URL, avatarData: AvatarInputProtocol, location: CLLocationCoordinate2D? = nil) { + init(mapStyleURL: URL, avatarData: AvatarInputProtocol, location: CLLocationCoordinate2D? = nil, isLiveLocationSharingEnabled: Bool = false) { var userAnnotation: UserLocationAnnotation? var annotations: [UserLocationAnnotation] = [] @@ -60,7 +60,8 @@ class LocationSharingViewModel: LocationSharingViewModelType, LocationSharingVie userAnnotation: userAnnotation, annotations: annotations, highlightedAnnotation: highlightedAnnotation, - showsUserLocation: showsUserLocation) + showsUserLocation: showsUserLocation, + isLiveLocationSharingEnabled: isLiveLocationSharingEnabled) super.init(initialViewState: viewState) diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/Test/UI/LocationSharingUITests.swift b/RiotSwiftUI/Modules/Room/LocationSharing/Test/UI/LocationSharingUITests.swift index d5040db2f3..469d470049 100644 --- a/RiotSwiftUI/Modules/Room/LocationSharing/Test/UI/LocationSharingUITests.swift +++ b/RiotSwiftUI/Modules/Room/LocationSharing/Test/UI/LocationSharingUITests.swift @@ -33,7 +33,6 @@ class LocationSharingUITests: XCTestCase { goToScreenWithIdentifier(MockLocationSharingScreenState.shareUserLocation.title) XCTAssertTrue(app.buttons["Cancel"].exists) - XCTAssertTrue(app.buttons["Share"].exists) XCTAssertTrue(app.otherElements["Map"].exists) } diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingUserMarkerView.swift b/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingMarkerView.swift similarity index 56% rename from RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingUserMarkerView.swift rename to RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingMarkerView.swift index 26bc101d3a..3c36e7d505 100644 --- a/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingUserMarkerView.swift +++ b/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingMarkerView.swift @@ -17,7 +17,7 @@ import SwiftUI @available(iOS 14.0, *) -struct LocationSharingUserMarkerView: View { +struct LocationSharingMarkerView: View { // MARK: - Properties @@ -25,21 +25,21 @@ struct LocationSharingUserMarkerView: View { @Environment(\.theme) private var theme: ThemeSwiftUI - @State private var frame: CGRect = .zero - // MARK: Public - let avatarData: AvatarInputProtocol + let backgroundColor: Color + @ViewBuilder var markerImage: Content var body: some View { ZStack { - Image(uiImage: Asset.Images.locationUserMarker.image) - AvatarImage(avatarData: avatarData, size: .large) - .offset(y: -1.5) + Rectangle() + .rotation(Angle(degrees: 45)) + .fill(backgroundColor) + .frame(width: 7, height: 7) + .offset(x: 0, y: 21) + markerImage + .frame(width: 40, height: 40) } - .background(ViewFrameReader(frame: $frame)) - .padding(.bottom, frame.height) - .accentColor(theme.colors.accent) } } @@ -49,9 +49,17 @@ struct LocationSharingUserMarkerView: View { struct LocationSharingUserMarkerView_Previews: PreviewProvider { static var previews: some View { let avatarData = AvatarInput(mxContentUri: "", - matrixItemId: "", + matrixItemId: "test", displayName: "Alice") - - LocationSharingUserMarkerView(avatarData: avatarData) + VStack(alignment: .center, spacing: 15) { + LocationSharingMarkerView(backgroundColor: .green) { + AvatarImage(avatarData: avatarData, size: .medium) + .border() + } + LocationSharingMarkerView(backgroundColor: .green) { + AvatarImage(avatarData: avatarData, size: .medium) + .border() + } + } } } diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingOptionButton.swift b/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingOptionButton.swift new file mode 100644 index 0000000000..32fde8f48f --- /dev/null +++ b/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingOptionButton.swift @@ -0,0 +1,69 @@ +// +// Copyright 2022 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 SwiftUI + +@available(iOS 14.0, *) +struct LocationSharingOptionButton: View { + + // MARK: - Properties + + // MARK: Private + + @Environment(\.theme) private var theme: ThemeSwiftUI + + let text: String + let action: () -> (Void) + @ViewBuilder var buttonIcon: Content + + var body: some View { + Button(action: action) { + HStack(spacing: 18) { + buttonIcon + .frame(width: 40, height: 40) + Text(text) + .font(theme.fonts.body) + .foregroundColor(theme.colors.primaryContent) + } + } + } +} + +@available(iOS 14.0, *) +struct LocationSharingOptionButton_Previews: PreviewProvider { + static var previews: some View { + VStack(alignment: .leading) { + LocationSharingOptionButton(text: VectorL10n.locationSharingStaticShareTitle) { + + } buttonIcon: { + AvatarImage(avatarData: AvatarInput(mxContentUri: nil, matrixItemId: "Alice", displayName: "Alice"), size: .medium) + .border() + } + LocationSharingOptionButton(text: VectorL10n.locationSharingLiveShareTitle) { + + } buttonIcon: { + Image(uiImage: Asset.Images.locationLiveIcon.image) + .resizable() + } + LocationSharingOptionButton(text: VectorL10n.locationSharingPinDropShareTitle) { + + } buttonIcon: { + Image(uiImage: Asset.Images.locationPinIcon.image) + .resizable() + } + } + } +} diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingView.swift b/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingView.swift index 317c20b025..ce3ae579c7 100644 --- a/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingView.swift +++ b/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingView.swift @@ -40,8 +40,14 @@ struct LocationSharingView: View { showsUserLocation: context.viewState.showsUserLocation, userLocation: $context.userLocation, errorSubject: context.viewState.errorSubject) - .ignoresSafeArea() - MapCreditsView() + VStack(spacing: 0) { + MapCreditsView() + if context.viewState.shareButtonVisible { + buttonsView + .background(theme.colors.background) + .clipShape(RoundedCornerShape(radius: 8, corners: [.topLeft, .topRight])) + } + } } .toolbar { ToolbarItem(placement: .navigationBarLeading) { @@ -63,11 +69,6 @@ struct LocationSharingView: View { .accessibilityIdentifier("LocationSharingView.shareButton") } .disabled(!context.viewState.shareButtonEnabled) - } else { - Button(VectorL10n.locationSharingShareAction, action: { - context.send(viewAction: .share) - }) - .disabled(!context.viewState.shareButtonEnabled) } } } @@ -84,6 +85,40 @@ struct LocationSharingView: View { .navigationViewStyle(StackNavigationViewStyle()) } + var buttonsView: some View { + VStack(alignment: .leading, spacing: 15) { + if !context.viewState.isPinDropSharing { + LocationSharingOptionButton(text: VectorL10n.locationSharingStaticShareTitle) { + context.send(viewAction: .share) + } buttonIcon: { + AvatarImage(avatarData: context.viewState.userAvatarData, size: .medium) + .border() + } + .disabled(!context.viewState.shareButtonEnabled) + // Hide for now until live location sharing is finished + if context.viewState.isLiveLocationSharingEnabled { + LocationSharingOptionButton(text: VectorL10n.locationSharingLiveShareTitle) { + // TODO: - Start live location sharing + } buttonIcon: { + Image(uiImage: Asset.Images.locationLiveIcon.image) + .resizable() + } + .disabled(!context.viewState.shareButtonEnabled) + } + } else { + LocationSharingOptionButton(text: VectorL10n.locationSharingPinDropShareTitle) { + // TODO: - Pin drop sharing action + } buttonIcon: { + Image(uiImage: Asset.Images.locationPinIcon.image) + .resizable() + } + .disabled(!context.viewState.shareButtonEnabled) + } + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding() + } + @ViewBuilder private var activityIndicator: some View { if context.viewState.showLoadingIndicator { @@ -98,6 +133,9 @@ struct LocationSharingView: View { struct LocationSharingView_Previews: PreviewProvider { static let stateRenderer = MockLocationSharingScreenState.stateRenderer static var previews: some View { - stateRenderer.screenGroup() + Group { + stateRenderer.screenGroup().theme(.light).preferredColorScheme(.light) + stateRenderer.screenGroup().theme(.dark).preferredColorScheme(.dark) + } } } diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/View/UserLocationAnnotatonView.swift b/RiotSwiftUI/Modules/Room/LocationSharing/View/UserLocationAnnotatonView.swift index 6835ca50c4..15c8e0593a 100644 --- a/RiotSwiftUI/Modules/Room/LocationSharing/View/UserLocationAnnotatonView.swift +++ b/RiotSwiftUI/Modules/Room/LocationSharing/View/UserLocationAnnotatonView.swift @@ -21,6 +21,10 @@ import Mapbox @available(iOS 14, *) class UserLocationAnnotatonView: MGLUserLocationAnnotationView { + // MARK: Private + + @Environment(\.theme) private var theme: ThemeSwiftUI + // MARK: - Setup init(avatarData: AvatarInputProtocol) { @@ -45,7 +49,10 @@ class UserLocationAnnotatonView: MGLUserLocationAnnotationView { private func addUserMarkerView(with avatarData: AvatarInputProtocol) { - guard let avatarImageView = UIHostingController(rootView: LocationSharingUserMarkerView(avatarData: avatarData)).view else { + guard let avatarImageView = UIHostingController(rootView: LocationSharingMarkerView(backgroundColor: theme.userColor(for: avatarData.matrixItemId)) { + AvatarImage(avatarData: avatarData, size: .medium) + .border() + }).view else { return } diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift index b324a9565f..aeea1b2622 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift @@ -35,7 +35,7 @@ struct TemplateRoomChatBubbleView: View { .accessibility(identifier: "bubbleImage") VStack(alignment: .leading){ Text(bubble.sender.displayName ?? "") - .foregroundColor(theme.displayNameColor(for: bubble.sender.id)) + .foregroundColor(theme.userColor(for: bubble.sender.id)) .font(theme.fonts.bodySB) ForEach(bubble.items) { item in TemplateRoomChatBubbleContentView(bubbleItem: item) diff --git a/changelog.d/5720.change b/changelog.d/5720.change new file mode 100644 index 0000000000..0b0eeee509 --- /dev/null +++ b/changelog.d/5720.change @@ -0,0 +1 @@ +Location Sharing: Update UI on location sharing view