diff --git a/CHANGES.md b/CHANGES.md
index 53b527969..040d7e5b4 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,10 @@
+## Changes in 1.8.7 (2022-03-18)
+
+🙌 Improvements
+
+- Room: Allow ignoring invited users that have not joined a room yet ([#5866](https://github.com/vector-im/element-ios/issues/5866))
+
+
## Changes in 1.8.6 (2022-03-14)
🙌 Improvements
diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig
index 164b076c5..e2f771c27 100644
--- a/Config/AppVersion.xcconfig
+++ b/Config/AppVersion.xcconfig
@@ -15,5 +15,5 @@
//
// Version
-MARKETING_VERSION = 1.8.7
-CURRENT_PROJECT_VERSION = 1.8.7
+MARKETING_VERSION = 1.8.8
+CURRENT_PROJECT_VERSION = 1.8.8
diff --git a/Podfile.lock b/Podfile.lock
index 1c3e87179..9b821e6f6 100644
--- a/Podfile.lock
+++ b/Podfile.lock
@@ -180,7 +180,7 @@ EXTERNAL SOURCES:
CHECKOUT OPTIONS:
AnalyticsEvents:
- :commit: 0101e4fd25ded5fb2cba8a9119cb061e36296369
+ :commit: f37a2f243270bffdcddfa5cbaeb4379e0db581c2
:git: https://github.com/matrix-org/matrix-analytics-events.git
SPEC CHECKSUMS:
@@ -227,4 +227,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 16aaf5e59ec902619fbfd799939f044728a92ab7
-COCOAPODS: 1.11.2
+COCOAPODS: 1.11.3
diff --git a/Riot/Assets/Images.xcassets/Room/Location/live_location_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Location/live_location_icon.imageset/Contents.json
new file mode 100644
index 000000000..e312c8132
--- /dev/null
+++ b/Riot/Assets/Images.xcassets/Room/Location/live_location_icon.imageset/Contents.json
@@ -0,0 +1,26 @@
+{
+ "images" : [
+ {
+ "filename" : "live_location_icon.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "live_location_icon@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "live_location_icon@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/live_location_icon.imageset/live_location_icon.png b/Riot/Assets/Images.xcassets/Room/Location/live_location_icon.imageset/live_location_icon.png
new file mode 100644
index 000000000..15d2d7b11
Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/live_location_icon.imageset/live_location_icon.png differ
diff --git a/Riot/Assets/Images.xcassets/Room/Location/live_location_icon.imageset/live_location_icon@2x.png b/Riot/Assets/Images.xcassets/Room/Location/live_location_icon.imageset/live_location_icon@2x.png
new file mode 100644
index 000000000..cc3a8fb4e
Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/live_location_icon.imageset/live_location_icon@2x.png differ
diff --git a/Riot/Assets/Images.xcassets/Room/Location/live_location_icon.imageset/live_location_icon@3x.png b/Riot/Assets/Images.xcassets/Room/Location/live_location_icon.imageset/live_location_icon@3x.png
new file mode 100644
index 000000000..59282b8f1
Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/live_location_icon.imageset/live_location_icon@3x.png differ
diff --git a/Riot/Assets/Images.xcassets/Room/Threads/threads_icon_gray_dot_dark.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Threads/threads_icon_gray_dot_dark.imageset/Contents.json
new file mode 100644
index 000000000..de2178f44
--- /dev/null
+++ b/Riot/Assets/Images.xcassets/Room/Threads/threads_icon_gray_dot_dark.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "dark-theme-no-mentions.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "original"
+ }
+}
diff --git a/Riot/Assets/Images.xcassets/Room/Threads/threads_icon_gray_dot_dark.imageset/dark-theme-no-mentions.svg b/Riot/Assets/Images.xcassets/Room/Threads/threads_icon_gray_dot_dark.imageset/dark-theme-no-mentions.svg
new file mode 100644
index 000000000..ae6aa847b
--- /dev/null
+++ b/Riot/Assets/Images.xcassets/Room/Threads/threads_icon_gray_dot_dark.imageset/dark-theme-no-mentions.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Riot/Assets/Images.xcassets/Room/Threads/threads_icon_gray_dot_light.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Threads/threads_icon_gray_dot_light.imageset/Contents.json
new file mode 100644
index 000000000..9d412b77e
--- /dev/null
+++ b/Riot/Assets/Images.xcassets/Room/Threads/threads_icon_gray_dot_light.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "light-theme-no-mentions.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "original"
+ }
+}
diff --git a/Riot/Assets/Images.xcassets/Room/Threads/threads_icon_gray_dot_light.imageset/light-theme-no-mentions.svg b/Riot/Assets/Images.xcassets/Room/Threads/threads_icon_gray_dot_light.imageset/light-theme-no-mentions.svg
new file mode 100644
index 000000000..f8468cbd2
--- /dev/null
+++ b/Riot/Assets/Images.xcassets/Room/Threads/threads_icon_gray_dot_light.imageset/light-theme-no-mentions.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Riot/Assets/Images.xcassets/Room/Threads/threads_icon_red_dot.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Threads/threads_icon_red_dot.imageset/Contents.json
new file mode 100644
index 000000000..dd53ab236
--- /dev/null
+++ b/Riot/Assets/Images.xcassets/Room/Threads/threads_icon_red_dot.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "light-and-dark-theme-mentions.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "original"
+ }
+}
diff --git a/Riot/Assets/Images.xcassets/Room/Threads/threads_icon_red_dot.imageset/light-and-dark-theme-mentions.svg b/Riot/Assets/Images.xcassets/Room/Threads/threads_icon_red_dot.imageset/light-and-dark-theme-mentions.svg
new file mode 100644
index 000000000..2bf8d2125
--- /dev/null
+++ b/Riot/Assets/Images.xcassets/Room/Threads/threads_icon_red_dot.imageset/light-and-dark-theme-mentions.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings
index af5aa32be..5f0fae942 100644
--- a/Riot/Assets/en.lproj/Vector.strings
+++ b/Riot/Assets/en.lproj/Vector.strings
@@ -2088,6 +2088,11 @@ Tap the + to start adding people.";
"location_sharing_settings_toggle_title" = "Enable location sharing";
+// MARK: Live location sharing
+
+"live_location_sharing_banner_title" = "Live location enabled";
+"live_location_sharing_banner_stop" = "Stop";
+
// MARK: - MatrixKit
diff --git a/Riot/Categories/Bundle.swift b/Riot/Categories/Bundle.swift
index 055d9c64c..5b3430154 100644
--- a/Riot/Categories/Bundle.swift
+++ b/Riot/Categories/Bundle.swift
@@ -30,4 +30,9 @@ public extension Bundle {
}
return bundle
}
+
+ /// Whether or not the bundle is the RiotShareExtension.
+ var isShareExtension: Bool {
+ bundleURL.lastPathComponent.contains("RiotShareExtension.appex")
+ }
}
diff --git a/Riot/Categories/UIButton.swift b/Riot/Categories/UIButton.swift
index 6f896bb58..195a2c244 100644
--- a/Riot/Categories/UIButton.swift
+++ b/Riot/Categories/UIButton.swift
@@ -14,7 +14,7 @@
limitations under the License.
*/
-import Foundation
+import UIKit
extension UIButton {
@@ -51,4 +51,10 @@ extension UIButton {
self.titleLabel?.adjustsFontForContentSizeCategory = newValue
}
}
+
+ /// Set title font and enable Dynamic Type support
+ func vc_setTitleFont(_ font: UIFont) {
+ self.vc_adjustsFontForContentSizeCategory = true
+ self.titleLabel?.font = font
+ }
}
diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift
index df3b02a27..0a78fb517 100644
--- a/Riot/Generated/Images.swift
+++ b/Riot/Generated/Images.swift
@@ -169,6 +169,7 @@ internal class Asset: NSObject {
internal static let videoCall = ImageAsset(name: "video_call")
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 locationMarkerIcon = ImageAsset(name: "location_marker_icon")
internal static let locationShareIcon = ImageAsset(name: "location_share_icon")
internal static let locationUserMarker = ImageAsset(name: "location_user_marker")
@@ -184,6 +185,9 @@ internal class Asset: NSObject {
internal static let threadsFilter = ImageAsset(name: "threads_filter")
internal static let threadsFilterApplied = ImageAsset(name: "threads_filter_applied")
internal static let threadsIcon = ImageAsset(name: "threads_icon")
+ internal static let threadsIconGrayDotDark = ImageAsset(name: "threads_icon_gray_dot_dark")
+ internal static let threadsIconGrayDotLight = ImageAsset(name: "threads_icon_gray_dot_light")
+ internal static let threadsIconRedDot = ImageAsset(name: "threads_icon_red_dot")
internal static let urlPreviewClose = ImageAsset(name: "url_preview_close")
internal static let urlPreviewCloseDark = ImageAsset(name: "url_preview_close_dark")
internal static let voiceMessageCancelGradient = ImageAsset(name: "voice_message_cancel_gradient")
diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift
index ca1a6125a..75d995cfa 100644
--- a/Riot/Generated/Strings.swift
+++ b/Riot/Generated/Strings.swift
@@ -2703,6 +2703,14 @@ public class VectorL10n: NSObject {
public static var less: String {
return VectorL10n.tr("Vector", "less")
}
+ /// Stop
+ public static var liveLocationSharingBannerStop: String {
+ return VectorL10n.tr("Vector", "live_location_sharing_banner_stop")
+ }
+ /// Live location enabled
+ public static var liveLocationSharingBannerTitle: String {
+ return VectorL10n.tr("Vector", "live_location_sharing_banner_title")
+ }
/// To discover contacts already using Matrix, %@ can send email addresses and phone numbers in your address book to your chosen Matrix identity server. Where supported, personal data is hashed before sending - please check your identity server's privacy policy for more details.
public static func localContactsAccessDiscoveryWarning(_ p1: String) -> String {
return VectorL10n.tr("Vector", "local_contacts_access_discovery_warning", p1)
diff --git a/Riot/Managers/Settings/RiotSettings.swift b/Riot/Managers/Settings/RiotSettings.swift
index 01a57f3c8..71f44b1d8 100644
--- a/Riot/Managers/Settings/RiotSettings.swift
+++ b/Riot/Managers/Settings/RiotSettings.swift
@@ -355,4 +355,10 @@ final class RiotSettings: NSObject {
@UserDefault(key: "slideMenuRoomsCoachMessageHasBeenDisplayed", defaultValue: false, storage: defaults)
var slideMenuRoomsCoachMessageHasBeenDisplayed
+
+ // MARK: - Metrics
+
+ /// Number of spaces previously tracked by the `AnalyticsSpaceTracker` instance.
+ @UserDefault(key: "lastNumberOfTrackedSpaces", defaultValue: nil, storage: defaults)
+ var lastNumberOfTrackedSpaces: Int?
}
diff --git a/Riot/Managers/Theme/Theme.swift b/Riot/Managers/Theme/Theme.swift
index f8e0a382d..93d9cb2a9 100644
--- a/Riot/Managers/Theme/Theme.swift
+++ b/Riot/Managers/Theme/Theme.swift
@@ -112,7 +112,7 @@ import DesignKit
/// - Parameter tabBar: The tab bar to customise.
func applyStyle(onTabBar tabBar: UITabBar)
- /// Apply the theme on a navigation bar, without enabling the iOS 15's scroll edges appearance.
+ /// Apply the theme on a navigation bar, without enabling the iOS 15's scroll edge appearance.
///
/// - Parameter navigationBar: the navigation bar to customise.
func applyStyle(onNavigationBar navigationBar: UINavigationBar)
@@ -120,9 +120,9 @@ import DesignKit
/// Apply the theme on a navigation bar.
///
/// - Parameter navigationBar: the navigation bar to customise.
- /// - Parameter modernScrollEdgesAppearance: whether or not to use the iOS 15 style scroll edges appearance
+ /// - Parameter modernScrollEdgeAppearance: whether or not to use the iOS 15 style scroll edge appearance
func applyStyle(onNavigationBar navigationBar: UINavigationBar,
- withModernScrollEdgesAppearance modernScrollEdgesAppearance: Bool)
+ withModernScrollEdgeAppearance modernScrollEdgeAppearance: Bool)
/// Apply the theme on a search bar.
///
diff --git a/Riot/Managers/Theme/Themes/DarkTheme.swift b/Riot/Managers/Theme/Themes/DarkTheme.swift
index ae19db7e7..d61ed3954 100644
--- a/Riot/Managers/Theme/Themes/DarkTheme.swift
+++ b/Riot/Managers/Theme/Themes/DarkTheme.swift
@@ -114,11 +114,11 @@ class DarkTheme: NSObject, Theme {
// Protocols don't support default parameter values and a protocol extension won't work for @objc
func applyStyle(onNavigationBar navigationBar: UINavigationBar) {
- applyStyle(onNavigationBar: navigationBar, withModernScrollEdgesAppearance: false)
+ applyStyle(onNavigationBar: navigationBar, withModernScrollEdgeAppearance: false)
}
func applyStyle(onNavigationBar navigationBar: UINavigationBar,
- withModernScrollEdgesAppearance modernScrollEdgesAppearance: Bool) {
+ withModernScrollEdgeAppearance modernScrollEdgeAppearance: Bool) {
navigationBar.tintColor = tintColor
// On iOS 15 use UINavigationBarAppearance to fix visual issues with the scrollEdgeAppearance style.
@@ -127,7 +127,7 @@ class DarkTheme: NSObject, Theme {
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = baseColor
- if !modernScrollEdgesAppearance {
+ if !modernScrollEdgeAppearance {
appearance.shadowColor = nil
}
appearance.titleTextAttributes = [
@@ -135,7 +135,7 @@ class DarkTheme: NSObject, Theme {
]
navigationBar.standardAppearance = appearance
- navigationBar.scrollEdgeAppearance = modernScrollEdgesAppearance ? nil : appearance
+ navigationBar.scrollEdgeAppearance = modernScrollEdgeAppearance ? nil : appearance
} else {
navigationBar.titleTextAttributes = [
NSAttributedString.Key.foregroundColor: textPrimaryColor
diff --git a/Riot/Managers/Theme/Themes/DefaultTheme.swift b/Riot/Managers/Theme/Themes/DefaultTheme.swift
index a7832ba07..e2afd9339 100644
--- a/Riot/Managers/Theme/Themes/DefaultTheme.swift
+++ b/Riot/Managers/Theme/Themes/DefaultTheme.swift
@@ -120,11 +120,11 @@ class DefaultTheme: NSObject, Theme {
// Protocols don't support default parameter values and a protocol extension doesn't work for @objc
func applyStyle(onNavigationBar navigationBar: UINavigationBar) {
- applyStyle(onNavigationBar: navigationBar, withModernScrollEdgesAppearance: false)
+ applyStyle(onNavigationBar: navigationBar, withModernScrollEdgeAppearance: false)
}
func applyStyle(onNavigationBar navigationBar: UINavigationBar,
- withModernScrollEdgesAppearance modernScrollEdgesAppearance: Bool) {
+ withModernScrollEdgeAppearance modernScrollEdgeAppearance: Bool) {
navigationBar.tintColor = tintColor
// On iOS 15 use UINavigationBarAppearance to fix visual issues with the scrollEdgeAppearance style.
@@ -133,7 +133,7 @@ class DefaultTheme: NSObject, Theme {
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = baseColor
- if !modernScrollEdgesAppearance {
+ if !modernScrollEdgeAppearance {
appearance.shadowColor = nil
}
appearance.titleTextAttributes = [
@@ -141,7 +141,7 @@ class DefaultTheme: NSObject, Theme {
]
navigationBar.standardAppearance = appearance
- navigationBar.scrollEdgeAppearance = modernScrollEdgesAppearance ? nil : appearance
+ navigationBar.scrollEdgeAppearance = modernScrollEdgeAppearance ? nil : appearance
} else {
navigationBar.titleTextAttributes = [
NSAttributedString.Key.foregroundColor: textPrimaryColor
diff --git a/Riot/Modules/Analytics/Analytics.swift b/Riot/Modules/Analytics/Analytics.swift
index c6296b28d..74880e69f 100644
--- a/Riot/Modules/Analytics/Analytics.swift
+++ b/Riot/Modules/Analytics/Analytics.swift
@@ -45,6 +45,8 @@ import AnalyticsEvents
/// The service used to interact with account data settings.
private var service: AnalyticsService?
+ private var viewRoomActiveSpace: AnalyticsViewRoomActiveSpace = .home
+
/// Whether or not the object is enabled and sending events to the server.
var isRunning: Bool { client.isRunning }
@@ -59,6 +61,31 @@ import AnalyticsEvents
RiotSettings.shared.hasAcceptedMatomoAnalytics
}
+ /// Used to defined the trigger of the next potential `JoinedRoom` event
+ var joinedRoomTrigger: AnalyticsJoinedRoomTrigger = .unknown
+
+ /// Used to defined the trigger of the next potential `ViewRoom` event
+ var viewRoomTrigger: AnalyticsViewRoomTrigger = .unknown
+
+ /// Used to defined the actual space activated by the user.
+ var activeSpace: MXSpace? {
+ didSet {
+ updateViewRoomActiveSpace()
+ }
+ }
+
+ /// Used to defined the currently visible space in explore rooms.
+ var exploringSpace: MXSpace? {
+ didSet {
+ updateViewRoomActiveSpace()
+ }
+ }
+
+ // MARK: - Private
+
+ /// keep an instance of `AnalyticsSpaceTracker` to track space metrics when space graph is built.
+ private let spaceTracker: AnalyticsSpaceTracker = AnalyticsSpaceTracker()
+
// MARK: - Public
/// Opts in to analytics tracking with the supplied session.
@@ -94,8 +121,13 @@ import AnalyticsEvents
MXLog.debug("[Analytics] Started.")
- // Catch and log crashes
- MXLogger.logCrashes(true)
+ if Bundle.main.isShareExtension {
+ // Don't log crashes in the share extension
+ } else {
+ // Catch and log crashes
+ MXLogger.logCrashes(true)
+ }
+
MXLogger.setBuildVersion(AppInfo.current.buildInfo.readableBuildVersion)
}
@@ -165,6 +197,19 @@ import AnalyticsEvents
private func capture(event: AnalyticsEventProtocol) {
client.capture(event)
}
+
+ /// Update `viewRoomActiveSpace` property according to the current value of `exploringSpace` and `activeSpace` properties.
+ private func updateViewRoomActiveSpace() {
+ let space = exploringSpace ?? activeSpace
+ guard let spaceRoom = space?.room else {
+ viewRoomActiveSpace = .home
+ return
+ }
+
+ spaceRoom.state { roomState in
+ self.viewRoomActiveSpace = roomState?.isJoinRulePublic == true ? .public : .private
+ }
+ }
}
// MARK: - Public tracking methods
@@ -174,10 +219,10 @@ extension Analytics {
/// Updates any user properties to help with creating cohorts.
///
/// Only non-nil properties will be updated when calling this method.
- func updateUserProperties(ftueUseCase: UserSessionProperties.UseCase? = nil) {
+ func updateUserProperties(ftueUseCase: UserSessionProperties.UseCase? = nil, numFavouriteRooms: Int? = nil, numSpaces: Int? = nil) {
let userProperties = AnalyticsEvent.UserProperties(ftueUseCaseSelection: ftueUseCase?.analyticsName,
- numFavouriteRooms: nil,
- numSpaces: nil)
+ numFavouriteRooms: numFavouriteRooms,
+ numSpaces: numSpaces)
client.updateUserProperties(userProperties)
}
@@ -242,6 +287,27 @@ extension Analytics {
func trackIdentityServerAccepted(_ accepted: Bool) {
// Do we still want to track this?
}
+
+ /// Track view room event triggered when the user changes rooms.
+ /// - Parameters:
+ /// - room: the room being viewed
+ func trackViewRoom(_ room: MXRoom) {
+ trackViewRoom(asDM: room.isDirect, isSpace: room.summary?.roomType == .space)
+ }
+
+ /// Track view room event triggered when the user changes rooms.
+ /// - Parameters:
+ /// - isDM: Whether the room is a DM.
+ /// - isSpace: Whether the room is a Space.
+ func trackViewRoom(asDM isDM: Bool, isSpace: Bool) {
+ let event = AnalyticsEvent.ViewRoom(activeSpace: viewRoomActiveSpace.space,
+ isDM: isDM,
+ isSpace: isSpace,
+ trigger: viewRoomTrigger.trigger,
+ viaKeyboard: nil)
+ viewRoomTrigger = .unknown
+ capture(event: event)
+ }
}
// MARK: - MXAnalyticsDelegate
@@ -284,8 +350,10 @@ extension Analytics: MXAnalyticsDelegate {
return
}
- let event = AnalyticsEvent.JoinedRoom(isDM: isDM, isSpace: isSpace, roomSize: roomSize, trigger: nil)
+ let event = AnalyticsEvent.JoinedRoom(isDM: isDM, isSpace: isSpace, roomSize: roomSize, trigger: joinedRoomTrigger.trigger)
capture(event: event)
+
+ self.joinedRoomTrigger = .unknown
}
/// **Note** This method isn't currently implemented.
diff --git a/Riot/Modules/Analytics/AnalyticsJoinedRoomTrigger.swift b/Riot/Modules/Analytics/AnalyticsJoinedRoomTrigger.swift
new file mode 100644
index 000000000..2b830870c
--- /dev/null
+++ b/Riot/Modules/Analytics/AnalyticsJoinedRoomTrigger.swift
@@ -0,0 +1,50 @@
+//
+// 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 Foundation
+import AnalyticsEvents
+
+@objc enum AnalyticsJoinedRoomTrigger: Int {
+ case unknown
+ case invite
+ case notification
+ case roomDirectory
+ case roomPreview
+ case slashCommand
+ case spaceHierarchy
+ case timeline
+
+ var trigger: AnalyticsEvent.JoinedRoom.Trigger? {
+ switch self {
+ case .unknown:
+ return nil
+ case .invite:
+ return .Invite
+ case .notification:
+ return .Notification
+ case .roomDirectory:
+ return .RoomDirectory
+ case .roomPreview:
+ return .RoomPreview
+ case .slashCommand:
+ return .SlashCommand
+ case .spaceHierarchy:
+ return .SpaceHierarchy
+ case .timeline:
+ return .Timeline
+ }
+ }
+}
diff --git a/Riot/Modules/Analytics/AnalyticsSpaceTracker.swift b/Riot/Modules/Analytics/AnalyticsSpaceTracker.swift
new file mode 100644
index 000000000..85aad36b7
--- /dev/null
+++ b/Riot/Modules/Analytics/AnalyticsSpaceTracker.swift
@@ -0,0 +1,47 @@
+//
+// 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 Foundation
+
+class AnalyticsSpaceTracker {
+
+ // MARK: - Setup
+
+ init() {
+ NotificationCenter.default.addObserver(self, selector: #selector(self.spaceGraphDidUpdate(notification:)), name: MXSpaceService.didBuildSpaceGraph, object: nil)
+ }
+
+ @objc private func spaceGraphDidUpdate(notification: Notification) {
+ guard let spaceService = notification.object as? MXSpaceService else {
+ return
+ }
+
+ trackSpaceNumber(with: spaceService)
+ }
+
+ // MARK: - Private
+
+ private func trackSpaceNumber(with spaceService: MXSpaceService) {
+ let spaceNumber = spaceService.spaceSummaries.filter { $0.membership == .join }.count
+
+ guard RiotSettings.shared.lastNumberOfTrackedSpaces != spaceNumber else {
+ return
+ }
+
+ Analytics.shared.updateUserProperties(numSpaces: spaceNumber)
+ RiotSettings.shared.lastNumberOfTrackedSpaces = spaceNumber
+ }
+}
diff --git a/Riot/Modules/Analytics/AnalyticsUIElement.swift b/Riot/Modules/Analytics/AnalyticsUIElement.swift
index bd44a7458..75a976126 100644
--- a/Riot/Modules/Analytics/AnalyticsUIElement.swift
+++ b/Riot/Modules/Analytics/AnalyticsUIElement.swift
@@ -22,6 +22,8 @@ import AnalyticsEvents
case roomThreadSummaryItem
case threadListThreadItem
case threadListFilterItem
+ case spacePanelSelectedSpace
+ case spacePanelSwitchSpace
/// The element name reported to the AnalyticsEvent.
var name: AnalyticsEvent.Interaction.Name {
@@ -34,6 +36,10 @@ import AnalyticsEvents
return .MobileThreadListThreadItem
case .threadListFilterItem:
return .MobileThreadListFilterItem
+ case .spacePanelSelectedSpace:
+ return .SpacePanelSelectedSpace
+ case .spacePanelSwitchSpace:
+ return .SpacePanelSwitchSpace
}
}
}
diff --git a/Riot/Modules/Analytics/AnalyticsViewRoomActiveSpace.swift b/Riot/Modules/Analytics/AnalyticsViewRoomActiveSpace.swift
new file mode 100644
index 000000000..20f541a15
--- /dev/null
+++ b/Riot/Modules/Analytics/AnalyticsViewRoomActiveSpace.swift
@@ -0,0 +1,41 @@
+//
+// 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 Foundation
+import AnalyticsEvents
+
+@objc enum AnalyticsViewRoomActiveSpace: Int {
+ case unknown
+ case home
+ case meta
+ case `private`
+ case `public`
+
+ var space: AnalyticsEvent.ViewRoom.ActiveSpace? {
+ switch self {
+ case .unknown:
+ return nil
+ case .home:
+ return .Home
+ case .meta:
+ return .Meta
+ case .private:
+ return .Private
+ case .public:
+ return .Public
+ }
+ }
+}
diff --git a/Riot/Modules/Analytics/AnalyticsViewRoomTrigger.swift b/Riot/Modules/Analytics/AnalyticsViewRoomTrigger.swift
new file mode 100644
index 000000000..1be27326a
--- /dev/null
+++ b/Riot/Modules/Analytics/AnalyticsViewRoomTrigger.swift
@@ -0,0 +1,89 @@
+//
+// 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 Foundation
+import AnalyticsEvents
+
+@objc enum AnalyticsViewRoomTrigger: Int {
+ case unknown
+ case created
+ case messageSearch
+ case messageUser
+ case notification
+ case predecessor
+ case roomDirectory
+ case roomList
+ case spaceHierarchy
+ case timeline
+ case tombstone
+ case verificationRequest
+ case widget
+ case roomMemberDetail
+ case fileSearch
+ case roomSearch
+ case searchContactDetail
+ case spaceMemberDetail
+ case inCall
+ case spaceMenu
+ case spaceSettings
+
+ var trigger: AnalyticsEvent.ViewRoom.Trigger? {
+ switch self {
+ case .unknown:
+ return nil
+ case .created:
+ return .Created
+ case .messageSearch:
+ return .MessageSearch
+ case .messageUser:
+ return .MessageUser
+ case .notification:
+ return .Notification
+ case .predecessor:
+ return .Predecessor
+ case .roomDirectory:
+ return .RoomDirectory
+ case .roomList:
+ return .RoomList
+ case .spaceHierarchy:
+ return .SpaceHierarchy
+ case .timeline:
+ return .Timeline
+ case .tombstone:
+ return .Tombstone
+ case .verificationRequest:
+ return .VerificationRequest
+ case .widget:
+ return .Widget
+ case .fileSearch:
+ return .MobileFileSearch
+ case .roomSearch:
+ return .MobileRoomSearch
+ case .roomMemberDetail:
+ return .MobileRoomMemberDetail
+ case .searchContactDetail:
+ return .MobileSearchContactDetail
+ case .spaceMemberDetail:
+ return .MobileSpaceMemberDetail
+ case .inCall:
+ return .MobileInCall
+ case .spaceMenu:
+ return .MobileSpaceMenu
+ case .spaceSettings:
+ return .MobileSpaceSettings
+ }
+ }
+}
diff --git a/Riot/Modules/Application/AppCoordinator.swift b/Riot/Modules/Application/AppCoordinator.swift
index 3413a90ad..fef10f5b8 100755
--- a/Riot/Modules/Application/AppCoordinator.swift
+++ b/Riot/Modules/Application/AppCoordinator.swift
@@ -213,9 +213,11 @@ final class AppCoordinator: NSObject, AppCoordinatorType {
case .homeSpace:
MXLog.verbose("Switch to home space")
self.navigateToSpace(with: nil)
+ Analytics.shared.activeSpace = nil
case .space(let spaceId):
MXLog.verbose("Switch to space with id: \(spaceId)")
self.navigateToSpace(with: spaceId)
+ Analytics.shared.activeSpace = userSessionsService.mainUserSession?.matrixSession.spaceService.getSpace(withId: spaceId)
}
}
diff --git a/Riot/Modules/Application/LegacyAppDelegate.m b/Riot/Modules/Application/LegacyAppDelegate.m
index 803dbea4c..52681b7d0 100644
--- a/Riot/Modules/Application/LegacyAppDelegate.m
+++ b/Riot/Modules/Application/LegacyAppDelegate.m
@@ -1109,6 +1109,19 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
shouldNavigateToRoomWithId:(NSString *)roomId
threadId:(NSString *)threadId
{
+ if (roomId)
+ {
+ MXRoom *room = [self.mxSessions.firstObject roomWithRoomId:roomId];
+ if (room.summary.membership != MXMembershipJoin)
+ {
+ Analytics.shared.joinedRoomTrigger = AnalyticsJoinedRoomTriggerNotification;
+ }
+ else
+ {
+ Analytics.shared.viewRoomTrigger = AnalyticsViewRoomTriggerNotification;
+ }
+ }
+
_lastNavigatedRoomIdFromPush = roomId;
[self navigateToRoomById:roomId threadId:threadId];
}
@@ -2955,6 +2968,11 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
{
MXRoom *room = [mxSession roomWithRoomId:roomId];
+ if (room && room.summary.membership == MXMembershipJoin)
+ {
+ [Analytics.shared trackViewRoom:room];
+ }
+
// Indicates that spaces are not supported
if (room.summary.roomType == MXRoomTypeSpace)
{
@@ -3186,6 +3204,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
[mxSession createRoomWithParameters:roomCreationParameters success:^(MXRoom *room) {
// Open created room
+ Analytics.shared.viewRoomTrigger = AnalyticsViewRoomTriggerCreated;
[self showRoom:room.roomId andEventId:nil withMatrixSession:mxSession];
if (completion)
@@ -3220,6 +3239,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
if (directRoom)
{
// open it
+ Analytics.shared.viewRoomTrigger = AnalyticsViewRoomTriggerCreated;
[self showRoom:directRoom.roomId andEventId:nil withMatrixSession:mxSession];
if (completion)
diff --git a/Riot/Modules/Authentication/AuthenticationViewController.m b/Riot/Modules/Authentication/AuthenticationViewController.m
index 448d75b8b..c8507dc5e 100644
--- a/Riot/Modules/Authentication/AuthenticationViewController.m
+++ b/Riot/Modules/Authentication/AuthenticationViewController.m
@@ -211,7 +211,7 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0;
- (void)userInterfaceThemeDidChange
{
[ThemeService.shared.theme applyStyleOnNavigationBar:self.navigationController.navigationBar
- withModernScrollEdgesAppearance:YES];
+ withModernScrollEdgeAppearance:YES];
self.view.backgroundColor = ThemeService.shared.theme.backgroundColor;
diff --git a/Riot/Modules/Call/CallViewController.m b/Riot/Modules/Call/CallViewController.m
index 8703e18c7..71c52ba72 100644
--- a/Riot/Modules/Call/CallViewController.m
+++ b/Riot/Modules/Call/CallViewController.m
@@ -563,6 +563,7 @@ CallAudioRouteMenuViewDelegate>
if (self.mxCall.room)
{
// Open the room page
+ Analytics.shared.viewRoomTrigger = AnalyticsViewRoomTriggerInCall;
[[AppDelegate theDelegate] showRoom:self.mxCall.room.roomId andEventId:nil withMatrixSession:self.mxCall.room.mxSession];
}
diff --git a/Riot/Modules/Common/Recents/RecentsViewController.m b/Riot/Modules/Common/Recents/RecentsViewController.m
index 9d3518a80..25aa17ec9 100644
--- a/Riot/Modules/Common/Recents/RecentsViewController.m
+++ b/Riot/Modules/Common/Recents/RecentsViewController.m
@@ -878,6 +878,12 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
- (void)showRoomWithRoomId:(NSString*)roomId inMatrixSession:(MXSession*)matrixSession
{
+ MXRoom *room = [matrixSession roomWithRoomId:roomId];
+ if (room.summary.membership == MXMembershipInvite)
+ {
+ Analytics.shared.joinedRoomTrigger = AnalyticsJoinedRoomTriggerInvite;
+ }
+
// Avoid multiple openings of rooms
self.userInteractionEnabled = NO;
@@ -897,6 +903,8 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
- (void)showRoomPreviewWithData:(RoomPreviewData*)roomPreviewData
{
+ Analytics.shared.joinedRoomTrigger = AnalyticsJoinedRoomTriggerRoomDirectory;
+
// Do not stack views when showing room
ScreenPresentationParameters *presentationParameters = [[ScreenPresentationParameters alloc] initWithRestoreInitialDisplay:NO stackAboveVisibleViews:NO sender:nil sourceView:nil];
@@ -993,6 +1001,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
}
// Accept invitation
+ Analytics.shared.joinedRoomTrigger = AnalyticsJoinedRoomTriggerInvite;
[self joinRoom:invitedRoom completion:nil];
}
else if ([actionIdentifier isEqualToString:kInviteRecentTableViewCellDeclineButtonPressed])
@@ -2060,6 +2069,8 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
// Check whether the user has already joined the selected public room
if ([self.recentsDataSource.publicRoomsDirectoryDataSource.mxSession isJoinedOnRoom:publicRoom.roomId])
{
+ Analytics.shared.viewRoomTrigger = AnalyticsViewRoomTriggerRoomDirectory;
+
// Open the public room
[self showRoomWithRoomId:publicRoom.roomId
inMatrixSession:self.recentsDataSource.publicRoomsDirectoryDataSource.mxSession];
@@ -2155,11 +2166,14 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
- (void)recentListViewController:(MXKRecentListViewController *)recentListViewController didSelectRoom:(NSString *)roomId inMatrixSession:(MXSession *)matrixSession
{
+ Analytics.shared.viewRoomTrigger = AnalyticsViewRoomTriggerRoomList;
[self showRoomWithRoomId:roomId inMatrixSession:matrixSession];
}
- (void)recentListViewController:(MXKRecentListViewController *)recentListViewController didSelectSuggestedRoom:(MXSpaceChildInfo *)childInfo
{
+ Analytics.shared.joinedRoomTrigger = AnalyticsJoinedRoomTriggerSpaceHierarchy;
+
RoomPreviewData *previewData = [[RoomPreviewData alloc] initWithSpaceChildInfo:childInfo andSession:self.mainSession];
[self startActivityIndicator];
MXWeakify(self);
@@ -2219,6 +2233,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
- (void)createRoomCoordinatorBridgePresenterDelegate:(CreateRoomCoordinatorBridgePresenter *)coordinatorBridgePresenter didCreateNewRoom:(MXRoom *)room
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
+ Analytics.shared.viewRoomTrigger = AnalyticsViewRoomTriggerCreated;
[self showRoomWithRoomId:room.roomId inMatrixSession:self.mainSession];
}];
coordinatorBridgePresenter = nil;
diff --git a/Riot/Modules/Common/SwiftUI/VectorHostingController.swift b/Riot/Modules/Common/SwiftUI/VectorHostingController.swift
index 6e5699e94..c79762b89 100644
--- a/Riot/Modules/Common/SwiftUI/VectorHostingController.swift
+++ b/Riot/Modules/Common/SwiftUI/VectorHostingController.swift
@@ -32,7 +32,7 @@ class VectorHostingController: UIHostingController {
// MARK: Public
- var enableNavigationBarScrollEdgesAppearance = false
+ var enableNavigationBarScrollEdgeAppearance = false
init(rootView: Content) where Content: View {
self.theme = ThemeService.shared().theme
@@ -93,7 +93,7 @@ class VectorHostingController: UIHostingController {
private func update(theme: Theme) {
if let navigationBar = self.navigationController?.navigationBar {
- theme.applyStyle(onNavigationBar: navigationBar, withModernScrollEdgesAppearance: enableNavigationBarScrollEdgesAppearance)
+ theme.applyStyle(onNavigationBar: navigationBar, withModernScrollEdgeAppearance: enableNavigationBarScrollEdgeAppearance)
}
}
}
diff --git a/Riot/Modules/Contacts/Details/ContactDetailsViewController.m b/Riot/Modules/Contacts/Details/ContactDetailsViewController.m
index bc1815a41..76b161caf 100644
--- a/Riot/Modules/Contacts/Details/ContactDetailsViewController.m
+++ b/Riot/Modules/Contacts/Details/ContactDetailsViewController.m
@@ -801,6 +801,7 @@
if (indexPath.row < directChatsArray.count)
{
// Open this room
+ Analytics.shared.viewRoomTrigger = AnalyticsViewRoomTriggerSearchContactDetail;
[[AppDelegate theDelegate] showRoom:directChatsArray[indexPath.row] andEventId:nil withMatrixSession:self.mainSession];
}
else
@@ -1053,7 +1054,8 @@
self->roomCreationRequest = nil;
[self removePendingActionMask];
-
+
+ Analytics.shared.viewRoomTrigger = AnalyticsViewRoomTriggerCreated;
[[AppDelegate theDelegate] showRoom:room.roomId andEventId:nil withMatrixSession:self.mainSession];
} failure:onFailure];
diff --git a/Riot/Modules/CreateRoom/CreateRoomCoordinator.swift b/Riot/Modules/CreateRoom/CreateRoomCoordinator.swift
index 8bc2f96d5..b86728e93 100644
--- a/Riot/Modules/CreateRoom/CreateRoomCoordinator.swift
+++ b/Riot/Modules/CreateRoom/CreateRoomCoordinator.swift
@@ -96,6 +96,7 @@ final class CreateRoomCoordinator: CreateRoomCoordinatorType {
TabbedRouterTab(title: VectorL10n.existing, icon: nil, module: roomSelectionCoordinator)
]
self.navigationRouter.setRootModule(self.tabRouter)
+ Analytics.shared.exploringSpace = parentSpace
} else {
self.navigationRouter.setRootModule(createRoomCoordinator)
}
diff --git a/Riot/Modules/Favorites/FavouritesViewController.m b/Riot/Modules/Favorites/FavouritesViewController.m
index eb9499359..49942eeed 100644
--- a/Riot/Modules/Favorites/FavouritesViewController.m
+++ b/Riot/Modules/Favorites/FavouritesViewController.m
@@ -130,9 +130,13 @@
[self.tableViewPaginationThrottler throttle:^{
NSInteger section = indexPath.section;
+ if (tableView.numberOfSections <= section)
+ {
+ return;
+ }
+
NSInteger numberOfRowsInSection = [tableView numberOfRowsInSection:section];
- if (tableView.numberOfSections > section
- && indexPath.row == numberOfRowsInSection - 1)
+ if (indexPath.row == numberOfRowsInSection - 1)
{
[self->recentsDataSource paginateInSection:section];
}
diff --git a/Riot/Modules/GlobalSearch/Files/HomeFilesSearchViewController.m b/Riot/Modules/GlobalSearch/Files/HomeFilesSearchViewController.m
index 84ab44030..04297d759 100644
--- a/Riot/Modules/GlobalSearch/Files/HomeFilesSearchViewController.m
+++ b/Riot/Modules/GlobalSearch/Files/HomeFilesSearchViewController.m
@@ -164,7 +164,7 @@
mxSession:session
threadParameters:threadParameters
presentationParameters:presentationParameters];
-
+ Analytics.shared.viewRoomTrigger = AnalyticsViewRoomTriggerFileSearch;
[[AppDelegate theDelegate] showRoomWithParameters:parameters];
}
diff --git a/Riot/Modules/GlobalSearch/Messages/HomeMessagesSearchViewController.m b/Riot/Modules/GlobalSearch/Messages/HomeMessagesSearchViewController.m
index c3c42fb07..ea0c6a2f3 100644
--- a/Riot/Modules/GlobalSearch/Messages/HomeMessagesSearchViewController.m
+++ b/Riot/Modules/GlobalSearch/Messages/HomeMessagesSearchViewController.m
@@ -171,6 +171,7 @@
mxSession:self.mainSession
threadParameters:threadParameters
presentationParameters:screenParameters];
+ Analytics.shared.viewRoomTrigger = AnalyticsViewRoomTriggerMessageSearch;
[[LegacyAppDelegate theDelegate] showRoomWithParameters:parameters];
}
diff --git a/Riot/Modules/GlobalSearch/Rooms/DirectoryViewController.m b/Riot/Modules/GlobalSearch/Rooms/DirectoryViewController.m
index e0ab0f218..876d80cab 100644
--- a/Riot/Modules/GlobalSearch/Rooms/DirectoryViewController.m
+++ b/Riot/Modules/GlobalSearch/Rooms/DirectoryViewController.m
@@ -243,11 +243,13 @@
mxSession:mxSession
threadParameters:nil
presentationParameters:presentationParameters];
+ Analytics.shared.viewRoomTrigger = AnalyticsViewRoomTriggerRoomDirectory;
[[AppDelegate theDelegate] showRoomWithParameters:parameters];
}
- (void)showRoomPreviewWithData:(RoomPreviewData*)roomPreviewData
{
+ Analytics.shared.joinedRoomTrigger = AnalyticsJoinedRoomTriggerRoomDirectory;
ScreenPresentationParameters *presentationParameters = [[ScreenPresentationParameters alloc] initWithRestoreInitialDisplay:NO stackAboveVisibleViews:NO];
RoomPreviewNavigationParameters *parameters = [[RoomPreviewNavigationParameters alloc] initWithPreviewData:roomPreviewData presentationParameters:presentationParameters];
diff --git a/Riot/Modules/Home/HomeViewController.m b/Riot/Modules/Home/HomeViewController.m
index 0f51fd248..22db94190 100644
--- a/Riot/Modules/Home/HomeViewController.m
+++ b/Riot/Modules/Home/HomeViewController.m
@@ -600,9 +600,13 @@
{
[self.collectionViewPaginationThrottler throttle:^{
NSInteger collectionViewSection = indexPath.section;
+ if (collectionView.numberOfSections <= collectionViewSection)
+ {
+ return;
+ }
+
NSInteger numberOfItemsInSection = [collectionView numberOfItemsInSection:collectionViewSection];
- if (collectionView.numberOfSections > collectionViewSection
- && indexPath.item == numberOfItemsInSection - 1)
+ if (indexPath.item == numberOfItemsInSection - 1)
{
NSInteger tableViewSection = collectionView.tag;
[self->recentsDataSource paginateInSection:tableViewSection];
diff --git a/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.m b/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.m
index 1a2466a51..e0664c9dd 100644
--- a/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.m
+++ b/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.m
@@ -1889,7 +1889,7 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) {
id stringLocalizer = [MXKSendReplyEventStringLocalizer new];
- [_room sendReplyToEvent:eventToReply withTextMessage:sanitizedText formattedTextMessage:html stringLocalizer:stringLocalizer localEcho:&localEchoEvent success:success failure:failure];
+ [_room sendReplyToEvent:eventToReply withTextMessage:sanitizedText formattedTextMessage:html stringLocalizer:stringLocalizer threadId:self.threadId localEcho:&localEchoEvent success:success failure:failure];
if (localEchoEvent)
{
diff --git a/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m b/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m
index 61b2a12ce..2e196bc59 100644
--- a/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m
+++ b/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m
@@ -1248,24 +1248,37 @@ static NSString *const kHTMLATagRegexPattern = @"([^<]*)";
}
else
{
+ NSDictionary *contentToUse;
+
+ if (event.content[kMXMessageContentKeyNewContent])
+ {
+ // use new content if exists
+ contentToUse = event.content[kMXMessageContentKeyNewContent];
+ }
+ else
+ {
+ // fallback to default content
+ contentToUse = event.content;
+ }
+
NSString *msgtype;
- MXJSONModelSetString(msgtype, event.content[kMXMessageTypeKey]);
+ MXJSONModelSetString(msgtype, contentToUse[kMXMessageTypeKey]);
NSString *body;
BOOL isHTML = NO;
NSString *eventThreadId = event.threadId;
// Use the HTML formatted string if provided
- if ([event.content[@"format"] isEqualToString:kMXRoomMessageFormatHTML])
+ if ([contentToUse[@"format"] isEqualToString:kMXRoomMessageFormatHTML])
{
isHTML =YES;
- MXJSONModelSetString(body, event.content[@"formatted_body"]);
+ MXJSONModelSetString(body, contentToUse[@"formatted_body"]);
}
else if (event.isReplyEvent || (eventThreadId && !RiotSettings.shared.enableThreads))
{
NSString *repliedEventId = event.relatesTo.inReplyTo.eventId ?: eventThreadId;
isHTML = YES;
- MXJSONModelSetString(body, event.content[kMXMessageBodyKey]);
+ MXJSONModelSetString(body, contentToUse[kMXMessageBodyKey]);
MXEvent *repliedEvent = [mxSession.store eventWithEventId:repliedEventId
inRoom:event.roomId];
@@ -1281,7 +1294,7 @@ static NSString *const kHTMLATagRegexPattern = @"([^<]*)";
}
else
{
- MXJSONModelSetString(body, event.content[kMXMessageBodyKey]);
+ MXJSONModelSetString(body, contentToUse[kMXMessageBodyKey]);
}
if (body)
@@ -1561,7 +1574,14 @@ static NSString *const kHTMLATagRegexPattern = @"([^<]*)";
else
{
NSString *body;
- MXJSONModelSetString(body, event.content[kMXMessageBodyKey]);
+ if (event.content[kMXMessageContentKeyNewContent])
+ {
+ MXJSONModelSetString(body, event.content[kMXMessageContentKeyNewContent][kMXMessageBodyKey]);
+ }
+ else
+ {
+ MXJSONModelSetString(body, event.content[kMXMessageBodyKey]);
+ }
// Check sticker validity
if (![self isSupportedAttachment:event])
@@ -1706,7 +1726,16 @@ static NSString *const kHTMLATagRegexPattern = @"([^<]*)";
return [self postRenderAttributedString:str];
}
- NSRange bodyRange = [str.string rangeOfString:event.content[kMXMessageBodyKey]];
+ NSString *body;
+ if (event.content[kMXMessageContentKeyNewContent])
+ {
+ MXJSONModelSetString(body, event.content[kMXMessageContentKeyNewContent][kMXMessageBodyKey]);
+ }
+ else
+ {
+ MXJSONModelSetString(body, event.content[kMXMessageBodyKey]);
+ }
+ NSRange bodyRange = [str.string rangeOfString:body];
if (bodyRange.location == NSNotFound)
{
// body not found in the whole string
@@ -1776,7 +1805,16 @@ static NSString *const kHTMLATagRegexPattern = @"([^<]*)";
return str;
}
- NSRange bodyRange = [str.string rangeOfString:event.content[kMXMessageBodyKey]];
+ NSString *body;
+ if (event.content[kMXMessageContentKeyNewContent])
+ {
+ MXJSONModelSetString(body, event.content[kMXMessageContentKeyNewContent][kMXMessageBodyKey]);
+ }
+ else
+ {
+ MXJSONModelSetString(body, event.content[kMXMessageBodyKey]);
+ }
+ NSRange bodyRange = [str.string rangeOfString:body];
if (bodyRange.location == NSNotFound)
{
// body not found in the whole string
@@ -2143,7 +2181,14 @@ static NSString *const kHTMLATagRegexPattern = @"([^<]*)";
else if (!_isForSubtitle && !string && event.eventType == MXEventTypeRoomMessage && (_emojiOnlyTextFont || _singleEmojiTextFont))
{
NSString *message;
- MXJSONModelSetString(message, event.content[kMXMessageBodyKey]);
+ if (event.content[kMXMessageContentKeyNewContent])
+ {
+ MXJSONModelSetString(message, event.content[kMXMessageContentKeyNewContent][kMXMessageBodyKey]);
+ }
+ else
+ {
+ MXJSONModelSetString(message, event.content[kMXMessageBodyKey]);
+ }
if (_emojiOnlyTextFont && [MXKTools isEmojiOnlyString:message])
{
diff --git a/Riot/Modules/People/PeopleViewController.m b/Riot/Modules/People/PeopleViewController.m
index fc6b5b69e..0f8152c5e 100644
--- a/Riot/Modules/People/PeopleViewController.m
+++ b/Riot/Modules/People/PeopleViewController.m
@@ -105,9 +105,13 @@
[self.tableViewPaginationThrottler throttle:^{
NSInteger section = indexPath.section;
+ if (tableView.numberOfSections <= section)
+ {
+ return;
+ }
+
NSInteger numberOfRowsInSection = [tableView numberOfRowsInSection:section];
- if (tableView.numberOfSections > section
- && indexPath.row == numberOfRowsInSection - 1)
+ if (indexPath.row == numberOfRowsInSection - 1)
{
[self->recentsDataSource paginateInSection:section];
}
diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m
index 2b4569fba..5e91b0d51 100644
--- a/Riot/Modules/Room/DataSources/RoomDataSource.m
+++ b/Riot/Modules/Room/DataSources/RoomDataSource.m
@@ -968,11 +968,6 @@ const CGFloat kTypingCellHeight = 24;
- (void)threadingService:(MXThreadingService *)service didCreateNewThread:(MXThread *)thread direction:(MXTimelineDirection)direction
{
- if (self.threadId)
- {
- // no need to reload the thread screen
- return;
- }
if (direction == MXTimelineDirectionBackwards)
{
// no need to reload when paginating back
diff --git a/Riot/Modules/Room/LocationSharing/LiveLocationSharingBannerView.swift b/Riot/Modules/Room/LocationSharing/LiveLocationSharingBannerView.swift
new file mode 100644
index 000000000..b6ad5e1c5
--- /dev/null
+++ b/Riot/Modules/Room/LocationSharing/LiveLocationSharingBannerView.swift
@@ -0,0 +1,96 @@
+//
+// 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 Foundation
+import Reusable
+import UIKit
+
+@objcMembers
+final class LiveLocationSharingBannerView: UIView, NibLoadable, Themable {
+
+ // MARK: - Properties
+
+ // MARK: Outlets
+
+ @IBOutlet private weak var iconImageView: UIImageView!
+ @IBOutlet private weak var titleLabel: UILabel!
+ @IBOutlet private weak var stopButton: UIButton!
+
+ // MARK: Private
+
+ private var theme: Theme!
+
+ // MARK: Public
+
+ var didTapBackground: (() -> Void)?
+ var didTapStopButton: (() -> Void)?
+
+ // MARK: - Setup
+
+ static func instantiate() -> LiveLocationSharingBannerView {
+ let view = LiveLocationSharingBannerView.loadFromNib()
+ view.update(theme: ThemeService.shared().theme)
+ return view
+ }
+
+ // MARK: - Life cycle
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+
+ self.setupBackgroundTapGestureRecognizer()
+
+ self.titleLabel.text = VectorL10n.liveLocationSharingBannerTitle
+ self.stopButton.setTitle(VectorL10n.liveLocationSharingBannerStop, for: .normal)
+ }
+
+ // MARK: - Public
+
+ func update(theme: Theme) {
+ self.theme = theme
+
+ let tintColor = theme.colors.background
+
+ self.backgroundColor = theme.tintColor
+
+ self.iconImageView.tintColor = tintColor
+
+ self.titleLabel.textColor = tintColor
+ self.titleLabel.font = theme.fonts.footnote
+
+ self.stopButton.vc_setTitleFont(theme.fonts.footnote)
+ self.stopButton.tintColor = tintColor
+ self.stopButton.setTitleColor(tintColor, for: .normal)
+ self.stopButton.setTitleColor(tintColor.withAlphaComponent(0.5), for: .highlighted)
+ }
+
+ // MARK: - Private
+
+ private func setupBackgroundTapGestureRecognizer() {
+ let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleBackgroundViewTap(_:)))
+ self.addGestureRecognizer(tapGestureRecognizer)
+ }
+
+ // MARK: - Actions
+
+ @objc private func handleBackgroundViewTap(_ gestureRecognizer: UITapGestureRecognizer) {
+ self.didTapBackground?()
+ }
+
+ @IBAction private func stopButtonAction(_ sender: Any) {
+ self.didTapStopButton?()
+ }
+}
diff --git a/Riot/Modules/Room/LocationSharing/LiveLocationSharingBannerView.xib b/Riot/Modules/Room/LocationSharing/LiveLocationSharingBannerView.xib
new file mode 100644
index 000000000..f6c67f287
--- /dev/null
+++ b/Riot/Modules/Room/LocationSharing/LiveLocationSharingBannerView.xib
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m
index 5fd62ecad..50f0871d5 100644
--- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m
+++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m
@@ -460,6 +460,7 @@
- (void)showRoomWithId:(NSString*)roomId
{
+ Analytics.shared.viewRoomTrigger = AnalyticsViewRoomTriggerRoomMemberDetail;
[[AppDelegate theDelegate] showRoom:roomId andEventId:nil withMatrixSession:self.mainSession];
}
@@ -629,7 +630,7 @@
}
// Check whether the option Ignore may be presented
- if (RiotSettings.shared.roomMemberScreenShowIgnore && self.mxRoomMember.membership == MXMembershipJoin)
+ if (RiotSettings.shared.roomMemberScreenShowIgnore)
{
// is he already ignored ?
if (![self.mainSession isUserIgnored:self.mxRoomMember.userId])
diff --git a/Riot/Modules/Room/RoomCoordinator.swift b/Riot/Modules/Room/RoomCoordinator.swift
index 84fd3ab62..c3799610a 100644
--- a/Riot/Modules/Room/RoomCoordinator.swift
+++ b/Riot/Modules/Room/RoomCoordinator.swift
@@ -459,4 +459,12 @@ extension RoomCoordinator: RoomViewControllerDelegate {
func roomViewControllerDidStopLoading(_ roomViewController: RoomViewController) {
stopLoading()
}
+
+ func roomViewControllerDidTapLiveLocationSharingBanner(_ roomViewController: RoomViewController) {
+ // TODO:
+ }
+
+ func roomViewControllerDidStopLiveLocationSharing(_ roomViewController: RoomViewController) {
+ // TODO:
+ }
}
diff --git a/Riot/Modules/Room/RoomViewController.h b/Riot/Modules/Room/RoomViewController.h
index de52c4978..1dcb69ece 100644
--- a/Riot/Modules/Room/RoomViewController.h
+++ b/Riot/Modules/Room/RoomViewController.h
@@ -277,6 +277,12 @@ didRequestEditForPollWithStartEvent:(MXEvent *)startEvent;
*/
- (void)roomViewControllerDidStopLoading:(RoomViewController *)roomViewController;
+/// User tap live location sharing stop action
+- (void)roomViewControllerDidStopLiveLocationSharing:(RoomViewController *)roomViewController;
+
+/// User tap live location sharing banner
+- (void)roomViewControllerDidTapLiveLocationSharingBanner:(RoomViewController *)roomViewController;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m
index c962e56d6..dbfb3cfc9 100644
--- a/Riot/Modules/Room/RoomViewController.m
+++ b/Riot/Modules/Room/RoomViewController.m
@@ -89,6 +89,10 @@
NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNotification";
NSNotificationName const RoomGroupCallTileTappedNotification = @"RoomGroupCallTileTappedNotification";
const NSTimeInterval kResizeComposerAnimationDuration = .05;
+static const int kThreadListBarButtonItemTag = 99;
+static UIEdgeInsets kThreadListBarButtonItemContentInsetsNoDot;
+static UIEdgeInsets kThreadListBarButtonItemContentInsetsDot;
+static CGSize kThreadListBarButtonItemImageSize;
@interface RoomViewController () 0)
{
- threadListBarButtonItem.badgeText = [self threadListBadgeTextFor:notificationsCount.numberOfHighlightedThreads];
- threadListBarButtonItem.badgeBackgroundColor = ThemeService.shared.theme.colors.alert;
+ [button setImage:AssetImages.threadsIconRedDot.image
+ forState:UIControlStateNormal];
+ button.contentEdgeInsets = kThreadListBarButtonItemContentInsetsDot;
}
else if (notificationsCount.numberOfNotifiedThreads > 0)
{
- threadListBarButtonItem.badgeText = [self threadListBadgeTextFor:notificationsCount.numberOfNotifiedThreads];
- threadListBarButtonItem.badgeBackgroundColor = ThemeService.shared.theme.noticeSecondaryColor;
+ if (ThemeService.shared.isCurrentThemeDark)
+ {
+ [button setImage:AssetImages.threadsIconGrayDotDark.image
+ forState:UIControlStateNormal];
+ }
+ else
+ {
+ [button setImage:AssetImages.threadsIconGrayDotLight.image
+ forState:UIControlStateNormal];
+ }
+ button.contentEdgeInsets = kThreadListBarButtonItemContentInsetsDot;
}
else
{
- // remove badge
- threadListBarButtonItem.badgeText = nil;
+ [button setImage:[AssetImages.threadsIcon.image vc_resizedWith:kThreadListBarButtonItemImageSize]
+ forState:UIControlStateNormal];
+ button.contentEdgeInsets = kThreadListBarButtonItemContentInsetsNoDot;
}
-}
-- (NSString *)threadListBadgeTextFor:(NSUInteger)numberOfThreads
-{
- if (numberOfThreads < 100)
+ if (replaceIndex == NSNotFound)
{
- return [NSString stringWithFormat:@"%tu", numberOfThreads];
+ // there is no thread list bar button item, this was only an update
+ return;
}
- else
+
+ UIBarButtonItem *originalItem = self.navigationItem.rightBarButtonItems[replaceIndex];
+ UIButton *originalButton = (UIButton *)originalItem.customView;
+ if ([originalButton imageForState:UIControlStateNormal] == [button imageForState:UIControlStateNormal]
+ && UIEdgeInsetsEqualToEdgeInsets(originalButton.contentEdgeInsets, button.contentEdgeInsets))
{
- return @"···";
+ // no need to replace, it's the same
+ return;
}
+ NSMutableArray *items = [self.navigationItem.rightBarButtonItems mutableCopy];
+ items[replaceIndex] = threadListBarButtonItem;
+ self.navigationItem.rightBarButtonItems = items;
}
#pragma mark - RoomContextualMenuViewControllerDelegate
@@ -7318,5 +7393,34 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
[self stopActivityIndicator];
}
-@end
+#pragma mark - Live location sharing
+- (void)showLiveLocationBannerView
+{
+ if (self.liveLocationSharingBannerView)
+ {
+ return;
+ }
+
+ LiveLocationSharingBannerView *bannerView = [LiveLocationSharingBannerView instantiate];
+
+ [bannerView updateWithTheme:ThemeService.shared.theme];
+
+ MXWeakify(self);
+
+ bannerView.didTapBackground = ^{
+ MXStrongifyAndReturnIfNil(self);
+ [self.delegate roomViewControllerDidTapLiveLocationSharingBanner:self];
+ };
+
+ bannerView.didTapStopButton = ^{
+ MXStrongifyAndReturnIfNil(self);
+ [self.delegate roomViewControllerDidStopLiveLocationSharing:self];
+ };
+
+ [self.topBannersStackView addArrangedSubview:bannerView];
+
+ self.liveLocationSharingBannerView = bannerView;
+}
+
+@end
diff --git a/Riot/Modules/Room/RoomViewController.xib b/Riot/Modules/Room/RoomViewController.xib
index e8b8bfd6e..783146c29 100644
--- a/Riot/Modules/Room/RoomViewController.xib
+++ b/Riot/Modules/Room/RoomViewController.xib
@@ -13,7 +13,6 @@
-
@@ -32,6 +31,7 @@
+
@@ -41,8 +41,25 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -192,6 +209,7 @@
+
@@ -200,17 +218,19 @@
+
+
-
+
diff --git a/Riot/Modules/Room/Search/RoomSearchViewController.m b/Riot/Modules/Room/Search/RoomSearchViewController.m
index eba0ee508..235d89798 100644
--- a/Riot/Modules/Room/Search/RoomSearchViewController.m
+++ b/Riot/Modules/Room/Search/RoomSearchViewController.m
@@ -164,6 +164,7 @@
mxSession:self.mainSession
threadParameters:threadParameters
presentationParameters:screenParameters];
+ Analytics.shared.viewRoomTrigger = AnalyticsViewRoomTriggerRoomSearch;
[[LegacyAppDelegate theDelegate] showRoomWithParameters:parameters];
}
diff --git a/Riot/Modules/Rooms/RoomsViewController.m b/Riot/Modules/Rooms/RoomsViewController.m
index 6e4e066d5..f7b54cf6a 100644
--- a/Riot/Modules/Rooms/RoomsViewController.m
+++ b/Riot/Modules/Rooms/RoomsViewController.m
@@ -110,9 +110,13 @@
[self.tableViewPaginationThrottler throttle:^{
NSInteger section = indexPath.section;
+ if (tableView.numberOfSections <= section)
+ {
+ return;
+ }
+
NSInteger numberOfRowsInSection = [tableView numberOfRowsInSection:section];
- if (tableView.numberOfSections > section
- && indexPath.row == numberOfRowsInSection - 1)
+ if (indexPath.row == numberOfRowsInSection - 1)
{
[self->recentsDataSource paginateInSection:section];
}
diff --git a/Riot/Modules/SideMenu/SideMenuCoordinator.swift b/Riot/Modules/SideMenu/SideMenuCoordinator.swift
index 7f5280fd8..7e3857d5f 100644
--- a/Riot/Modules/SideMenu/SideMenuCoordinator.swift
+++ b/Riot/Modules/SideMenu/SideMenuCoordinator.swift
@@ -309,6 +309,7 @@ final class SideMenuCoordinator: NSObject, SideMenuCoordinatorType {
coordinator.toPresentable().dismiss(animated: true) {
self.spaceSettingsCoordinator = nil
+ self.resetExploringSpaceIfNeeded()
}
}
@@ -319,6 +320,12 @@ final class SideMenuCoordinator: NSObject, SideMenuCoordinatorType {
self.spaceSettingsCoordinator = coordinator
}
+ private func resetExploringSpaceIfNeeded() {
+ if sideMenuNavigationViewController.presentedViewController == nil {
+ Analytics.shared.exploringSpace = nil
+ }
+ }
+
// MARK: UserSessions management
private func registerUserSessionsServiceNotifications() {
@@ -407,8 +414,10 @@ extension SideMenuCoordinator: SpaceMenuPresenterDelegate {
presenter.dismiss(animated: false) {
switch action {
case .exploreRooms:
+ Analytics.shared.viewRoomTrigger = .spaceMenu
self.showExploreRooms(spaceId: spaceId, session: session)
case .exploreMembers:
+ Analytics.shared.viewRoomTrigger = .spaceMenu
self.showMembers(spaceId: spaceId, session: session)
case .addRoom:
session.spaceService.getSpace(withId: spaceId)?.canAddRoom { canAddRoom in
@@ -453,6 +462,7 @@ extension SideMenuCoordinator: ExploreRoomCoordinatorDelegate {
func exploreRoomCoordinatorDidComplete(_ coordinator: ExploreRoomCoordinatorType) {
self.exploreRoomCoordinator?.toPresentable().dismiss(animated: true) {
self.exploreRoomCoordinator = nil
+ self.resetExploringSpaceIfNeeded()
}
}
}
@@ -462,6 +472,7 @@ extension SideMenuCoordinator: SpaceMembersCoordinatorDelegate {
func spaceMembersCoordinatorDidCancel(_ coordinator: SpaceMembersCoordinatorType) {
self.membersCoordinator?.toPresentable().dismiss(animated: true) {
self.membersCoordinator = nil
+ self.resetExploringSpaceIfNeeded()
}
}
}
@@ -472,7 +483,7 @@ extension SideMenuCoordinator: CreateRoomCoordinatorDelegate {
coordinator.toPresentable().dismiss(animated: true) {
self.createRoomCoordinator = nil
self.parameters.appNavigator.sideMenu.dismiss(animated: true) {
-
+ self.resetExploringSpaceIfNeeded()
}
if let spaceId = coordinator.parentSpace?.spaceId {
self.parameters.appNavigator.navigate(to: .space(spaceId))
@@ -484,7 +495,7 @@ extension SideMenuCoordinator: CreateRoomCoordinatorDelegate {
coordinator.toPresentable().dismiss(animated: true) {
self.createRoomCoordinator = nil
self.parameters.appNavigator.sideMenu.dismiss(animated: true) {
-
+ self.resetExploringSpaceIfNeeded()
}
if let spaceId = coordinator.parentSpace?.spaceId {
self.parameters.appNavigator.navigate(to: .space(spaceId))
@@ -495,6 +506,7 @@ extension SideMenuCoordinator: CreateRoomCoordinatorDelegate {
func createRoomCoordinatorDidCancel(_ coordinator: CreateRoomCoordinatorType) {
coordinator.toPresentable().dismiss(animated: true) {
self.createRoomCoordinator = nil
+ self.resetExploringSpaceIfNeeded()
}
}
}
@@ -508,5 +520,6 @@ extension SideMenuCoordinator: UIAdaptivePresentationControllerDelegate {
self.createSpaceCoordinator = nil
self.createRoomCoordinator = nil
self.spaceSettingsCoordinator = nil
+ self.resetExploringSpaceIfNeeded()
}
}
diff --git a/Riot/Modules/SideMenu/SideMenuViewAction.swift b/Riot/Modules/SideMenu/SideMenuViewAction.swift
index b494e07dc..45adc68fa 100644
--- a/Riot/Modules/SideMenu/SideMenuViewAction.swift
+++ b/Riot/Modules/SideMenu/SideMenuViewAction.swift
@@ -22,4 +22,5 @@ import Foundation
enum SideMenuViewAction {
case loadData
case tap(menuItem: SideMenuItem, sourceView: UIView)
+ case tapHeader(sourceView: UIView)
}
diff --git a/Riot/Modules/SideMenu/SideMenuViewController.storyboard b/Riot/Modules/SideMenu/SideMenuViewController.storyboard
index 998474b8d..579a3332f 100644
--- a/Riot/Modules/SideMenu/SideMenuViewController.storyboard
+++ b/Riot/Modules/SideMenu/SideMenuViewController.storyboard
@@ -1,9 +1,9 @@
-
+
-
+
@@ -28,29 +28,42 @@
-
+
+
+
+
+
+
@@ -114,7 +127,7 @@
-
+
diff --git a/Riot/Modules/SideMenu/SideMenuViewController.swift b/Riot/Modules/SideMenu/SideMenuViewController.swift
index f92be3bbd..344fb3764 100644
--- a/Riot/Modules/SideMenu/SideMenuViewController.swift
+++ b/Riot/Modules/SideMenu/SideMenuViewController.swift
@@ -198,6 +198,10 @@ final class SideMenuViewController: UIViewController {
// MARK: - Actions
+
+ @IBAction func headerTapAction(sender: UIView) {
+ self.viewModel.process(viewAction: .tapHeader(sourceView: sender))
+ }
}
// MARK: - SideMenuViewModelViewDelegate
diff --git a/Riot/Modules/SideMenu/SideMenuViewModel.swift b/Riot/Modules/SideMenu/SideMenuViewModel.swift
index d1b013f97..0b0b84862 100644
--- a/Riot/Modules/SideMenu/SideMenuViewModel.swift
+++ b/Riot/Modules/SideMenu/SideMenuViewModel.swift
@@ -53,6 +53,8 @@ final class SideMenuViewModel: SideMenuViewModelType {
self.loadData()
case .tap(menuItem: let menuItem, sourceView: let sourceView):
self.coordinatorDelegate?.sideMenuViewModel(self, didTapMenuItem: menuItem, fromSourceView: sourceView)
+ case .tapHeader(sourceView: let sourceView):
+ self.coordinatorDelegate?.sideMenuViewModel(self, didTapMenuItem: .settings, fromSourceView: sourceView)
}
}
diff --git a/Riot/Modules/Spaces/SpaceList/SpaceListViewModel.swift b/Riot/Modules/Spaces/SpaceList/SpaceListViewModel.swift
index 911529a45..78594ac5f 100644
--- a/Riot/Modules/Spaces/SpaceList/SpaceListViewModel.swift
+++ b/Riot/Modules/Spaces/SpaceList/SpaceListViewModel.swift
@@ -37,7 +37,7 @@ final class SpaceListViewModel: SpaceListViewModelType {
private var sections: [SpaceListSection] = []
private var selectedIndexPath: IndexPath = IndexPath(row: 0, section: 0) {
didSet {
- self.selectedItemId = self.itemId(with: self.selectedIndexPath)
+ self.selectedItemId = self.itemId(with: self.selectedIndexPath) ?? Constants.homeSpaceId
}
}
private var homeIndexPath: IndexPath = IndexPath(row: 0, section: 0)
@@ -73,13 +73,16 @@ final class SpaceListViewModel: SpaceListViewModelType {
self.loadData()
case .selectRow(at: let indexPath, from: let sourceView):
guard self.selectedIndexPath != indexPath else {
+ Analytics.shared.trackInteraction(.spacePanelSelectedSpace)
return
}
+
let section = self.sections[indexPath.section]
switch section {
case .home:
self.selectHome()
self.selectedIndexPath = indexPath
+ Analytics.shared.trackInteraction(.spacePanelSwitchSpace)
self.update(viewState: .selectionChanged(indexPath))
case .spaces(let viewDataList):
let spaceViewData = viewDataList[indexPath.row]
@@ -88,6 +91,7 @@ final class SpaceListViewModel: SpaceListViewModelType {
} else {
self.selectSpace(with: spaceViewData.spaceId)
self.selectedIndexPath = indexPath
+ Analytics.shared.trackInteraction(.spacePanelSwitchSpace)
self.update(viewState: .selectionChanged(indexPath))
}
case .addSpace:
@@ -270,7 +274,7 @@ final class SpaceListViewModel: SpaceListViewModelType {
self.currentOperation?.cancel()
}
- private func itemId(with indexPath: IndexPath) -> String {
+ private func itemId(with indexPath: IndexPath) -> String? {
guard self.selectedIndexPath.section < self.sections.count else {
return Constants.homeSpaceId
}
@@ -279,6 +283,9 @@ final class SpaceListViewModel: SpaceListViewModelType {
case .home:
return Constants.homeSpaceId
case .spaces(let viewDataList):
+ guard self.selectedIndexPath.row < viewDataList.count else {
+ return nil
+ }
let spaceViewData = viewDataList[self.selectedIndexPath.row]
return spaceViewData.spaceId
case .addSpace:
diff --git a/Riot/Modules/Spaces/SpaceMembers/MemberDetail/SpaceMemberDetailViewModel.swift b/Riot/Modules/Spaces/SpaceMembers/MemberDetail/SpaceMemberDetailViewModel.swift
index 114584869..cee72964d 100644
--- a/Riot/Modules/Spaces/SpaceMembers/MemberDetail/SpaceMemberDetailViewModel.swift
+++ b/Riot/Modules/Spaces/SpaceMembers/MemberDetail/SpaceMemberDetailViewModel.swift
@@ -59,6 +59,7 @@ final class SpaceMemberDetailViewModel: NSObject, SpaceMemberDetailViewModelType
case .loadData:
self.loadData()
case .openRoom(let roomId):
+ Analytics.shared.viewRoomTrigger = .spaceMemberDetail
self.coordinatorDelegate?.spaceMemberDetailViewModel(self, showRoomWithId: roomId)
case .createRoom(let memberId):
self.createDirectRoom(forMemberWithId: memberId)
@@ -108,6 +109,7 @@ final class SpaceMemberDetailViewModel: NSObject, SpaceMemberDetailViewModelType
}
return
}
+ Analytics.shared.viewRoomTrigger = .created
self.coordinatorDelegate?.spaceMemberDetailViewModel(self, showRoomWithId: room.roomId)
}
} failure: { error in
diff --git a/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewController.swift b/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewController.swift
index c4419b870..dce516a80 100644
--- a/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewController.swift
+++ b/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewController.swift
@@ -75,6 +75,7 @@ final class SpaceMemberListViewController: RoomParticipantsViewController {
super.viewWillAppear(animated)
AnalyticsScreenTracker.trackScreen(.spaceMembers)
+ Analytics.shared.exploringSpace = viewModel.space
}
override var preferredStatusBarStyle: UIStatusBarStyle {
diff --git a/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewModel.swift b/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewModel.swift
index 91b555928..362bf56df 100644
--- a/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewModel.swift
+++ b/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewModel.swift
@@ -32,6 +32,9 @@ final class SpaceMemberListViewModel: SpaceMemberListViewModelType {
// MARK: Public
+ var space: MXSpace? {
+ return session.spaceService.getSpace(withId: spaceId)
+ }
weak var viewDelegate: SpaceMemberListViewModelViewDelegate?
weak var coordinatorDelegate: SpaceMemberListViewModelCoordinatorDelegate?
diff --git a/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewModelType.swift b/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewModelType.swift
index 4b4a0e21c..3cbecf47f 100644
--- a/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewModelType.swift
+++ b/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewModelType.swift
@@ -33,6 +33,7 @@ protocol SpaceMemberListViewModelType {
var viewDelegate: SpaceMemberListViewModelViewDelegate? { get set }
var coordinatorDelegate: SpaceMemberListViewModelCoordinatorDelegate? { get set }
+ var space: MXSpace? { get }
func process(viewAction: SpaceMemberListViewAction)
}
diff --git a/Riot/Modules/Spaces/SpaceMembers/SpaceMembersCoordinator.swift b/Riot/Modules/Spaces/SpaceMembers/SpaceMembersCoordinator.swift
index 4b16e5f81..f9dde8858 100644
--- a/Riot/Modules/Spaces/SpaceMembers/SpaceMembersCoordinator.swift
+++ b/Riot/Modules/Spaces/SpaceMembers/SpaceMembersCoordinator.swift
@@ -127,6 +127,10 @@ final class SpaceMembersCoordinator: SpaceMembersCoordinatorType {
let roomDataSourceManager = MXKRoomDataSourceManager.sharedManager(forMatrixSession: self.parameters.session)
roomDataSourceManager?.roomDataSource(forRoom: roomId, create: true, onComplete: { [weak self] roomDataSource in
+ if let room = self?.parameters.session.room(withRoomId: roomId) {
+ Analytics.shared.trackViewRoom(room)
+ }
+
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
guard let roomViewController = storyboard.instantiateViewController(withIdentifier: "RoomViewControllerStoryboardId") as? RoomViewController else {
return
diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewController.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewController.swift
index bea9711f0..b19977e00 100644
--- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewController.swift
+++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewController.swift
@@ -88,6 +88,15 @@ final class SpaceExploreRoomViewController: UIViewController {
AnalyticsScreenTracker.trackScreen(.spaceExploreRooms)
}
+ override func viewDidAppear(_ animated: Bool) {
+ super.viewDidAppear(animated)
+
+ if let spaceRoom = self.viewModel.space?.room {
+ Analytics.shared.trackViewRoom(spaceRoom)
+ }
+ Analytics.shared.exploringSpace = self.viewModel.space
+ }
+
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift
index 3a3733118..57fd88f15 100644
--- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift
+++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift
@@ -71,6 +71,9 @@ final class SpaceExploreRoomViewModel: SpaceExploreRoomViewModelType {
}
private var spaceGraphObserver: Any?
+ var space: MXSpace? {
+ return session.spaceService.getSpace(withId: spaceId)
+ }
// MARK: Public
diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModelType.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModelType.swift
index c545c6a54..5d22aa6f6 100644
--- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModelType.swift
+++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModelType.swift
@@ -17,6 +17,7 @@
*/
import Foundation
+import MatrixSDK
protocol SpaceExploreRoomViewModelViewDelegate: AnyObject {
func spaceExploreRoomViewModel(_ viewModel: SpaceExploreRoomViewModelType, didUpdateViewState viewSate: SpaceExploreRoomViewState)
@@ -37,6 +38,7 @@ protocol SpaceExploreRoomViewModelType {
var viewDelegate: SpaceExploreRoomViewModelViewDelegate? { get set }
var coordinatorDelegate: SpaceExploreRoomViewModelCoordinatorDelegate? { get set }
var showCancelMenuItem: Bool { get }
+ var space: MXSpace? { get }
func process(viewAction: SpaceExploreRoomViewAction)
@available(iOS 13.0, *)
diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinator.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinator.swift
index 71f560749..8cd8cc824 100644
--- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinator.swift
+++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinator.swift
@@ -116,6 +116,7 @@ final class ExploreRoomCoordinator: NSObject, ExploreRoomCoordinatorType {
}
private func showRoomPreview(with item: SpaceExploreRoomListItemViewData, from sourceView: UIView?) {
+ Analytics.shared.joinedRoomTrigger = .spaceHierarchy
let coordinator = self.createShowSpaceRoomDetailCoordinator(session: self.session, childInfo: item.childInfo)
coordinator.start()
self.add(childCoordinator: coordinator)
@@ -151,6 +152,11 @@ final class ExploreRoomCoordinator: NSObject, ExploreRoomCoordinatorType {
let roomDataSourceManager = MXKRoomDataSourceManager.sharedManager(forMatrixSession: self.session)
roomDataSourceManager?.roomDataSource(forRoom: roomId, create: true, onComplete: { [weak self] roomDataSource in
+ if let room = self?.session.room(withRoomId: roomId) {
+ Analytics.shared.viewRoomTrigger = .spaceHierarchy
+ Analytics.shared.trackViewRoom(room)
+ }
+
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
guard let roomViewController = storyboard.instantiateViewController(withIdentifier: "RoomViewControllerStoryboardId") as? RoomViewController else {
return
@@ -468,6 +474,13 @@ extension ExploreRoomCoordinator: RoomViewControllerDelegate {
}
+ func roomViewControllerDidTapLiveLocationSharingBanner(_ roomViewController: RoomViewController) {
+ // TODO:
+ }
+
+ func roomViewControllerDidStopLiveLocationSharing(_ roomViewController: RoomViewController) {
+ // TODO:
+ }
}
// MARK: - ContactsPickerCoordinatorDelegate
diff --git a/Riot/Modules/StartChat/StartChatViewController.m b/Riot/Modules/StartChat/StartChatViewController.m
index 216de7b47..c70bbdc83 100644
--- a/Riot/Modules/StartChat/StartChatViewController.m
+++ b/Riot/Modules/StartChat/StartChatViewController.m
@@ -648,6 +648,7 @@
[self stopActivityIndicator];
+ Analytics.shared.viewRoomTrigger = AnalyticsViewRoomTriggerCreated;
[[AppDelegate theDelegate] showRoom:room.roomId andEventId:nil withMatrixSession:self.mainSession];
} failure:onFailure];
diff --git a/Riot/Modules/Threads/ThreadsCoordinator.swift b/Riot/Modules/Threads/ThreadsCoordinator.swift
index 2f49786c5..e28d815fd 100644
--- a/Riot/Modules/Threads/ThreadsCoordinator.swift
+++ b/Riot/Modules/Threads/ThreadsCoordinator.swift
@@ -68,6 +68,10 @@ final class ThreadsCoordinator: NSObject, ThreadsCoordinatorProtocol {
// Detect when view controller has been dismissed by gesture when presented modally (not in full screen).
self.navigationRouter.toPresentable().presentationController?.delegate = self
+ guard parameters.threadId == nil else {
+ return
+ }
+
if self.navigationRouter.modules.isEmpty == false {
self.navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in
self?.remove(childCoordinator: rootCoordinator)
diff --git a/RiotShareExtension/Shared/ShareDataSource.h b/RiotShareExtension/Shared/ShareDataSource.h
index 9dc36d5f5..92aef0c1d 100644
--- a/RiotShareExtension/Shared/ShareDataSource.h
+++ b/RiotShareExtension/Shared/ShareDataSource.h
@@ -31,7 +31,7 @@
@property (nonatomic, strong, readonly) NSSet *selectedRoomIdentifiers;
- (instancetype)initWithFileStore:(MXFileStore *)fileStore
- credentials:(MXCredentials *)credentials;
+ session:(MXSession *)session;
- (void)selectRoomWithIdentifier:(NSString *)roomIdentifier animated:(BOOL)animated;
diff --git a/RiotShareExtension/Shared/ShareDataSource.m b/RiotShareExtension/Shared/ShareDataSource.m
index 83889c333..e097b5da7 100644
--- a/RiotShareExtension/Shared/ShareDataSource.m
+++ b/RiotShareExtension/Shared/ShareDataSource.m
@@ -20,7 +20,7 @@
@interface ShareDataSource ()
@property (nonatomic, strong, readonly) MXFileStore *fileStore;
-@property (nonatomic, strong, readonly) MXCredentials *credentials;
+@property (nonatomic, strong, readonly) MXSession *session;
@property NSArray *recentCellDatas;
@property NSMutableArray *visibleRoomCellDatas;
@@ -32,12 +32,12 @@
@implementation ShareDataSource
- (instancetype)initWithFileStore:(MXFileStore *)fileStore
- credentials:(MXCredentials *)credentials
+ session:(MXSession *)session
{
if (self = [super init])
{
_fileStore = fileStore;
- _credentials = credentials;
+ _session = session;
_internalSelectedRoomIdentifiers = [NSMutableSet set];
@@ -81,19 +81,13 @@
NSMutableArray *cellData = [NSMutableArray array];
- MXRestClient *mxRestClient = [[MXRestClient alloc] initWithCredentials:self.credentials andOnUnrecognizedCertificateBlock:nil andPersistentTokenDataHandler:^(void (^handler)(NSArray *credentials, void (^completion)(BOOL didUpdateCredentials))) {
- [[MXKAccountManager sharedManager] readAndWriteCredentials:handler];
- } andUnauthenticatedHandler:nil];
- // Add a fake matrix session to each room summary to provide it a REST client (used to handle correctly the room avatar).
- MXSession *session = [[MXSession alloc] initWithMatrixRestClient:mxRestClient];
-
for (id summary in summaries)
{
if (!summary.hiddenFromUser && summary.roomType == MXRoomTypeRoom)
{
if ([summary respondsToSelector:@selector(setMatrixSession:)])
{
- [summary setMatrixSession:session];
+ [summary setMatrixSession:self.session];
}
MXKRecentCellData *recentCellData = [[MXKRecentCellData alloc] initWithRoomSummary:summary dataSource:nil];
diff --git a/RiotShareExtension/Shared/ShareManager.m b/RiotShareExtension/Shared/ShareManager.m
index ad7a75ad3..2233b352d 100644
--- a/RiotShareExtension/Shared/ShareManager.m
+++ b/RiotShareExtension/Shared/ShareManager.m
@@ -34,11 +34,21 @@
@property (nonatomic, strong) MXKAccount *userAccount;
@property (nonatomic, strong) MXFileStore *fileStore;
+/**
+ An array of rooms that the item is being shared to. This is to maintain a strong ref
+ to all necessary `MXRoom`s until sharing has completed.
+ */
+@property (nonatomic, strong) NSMutableArray *selectedRooms;
+
@end
@implementation ShareManager
+/// A fake matrix session used to provide summaries with a REST client to handle room avatars.
+/// The session is stored statically to prevent new ones from being created for each share.
+static MXSession *fakeSession;
+
- (instancetype)initWithShareItemSender:(id)itemSender
type:(ShareManagerType)type
{
@@ -94,17 +104,19 @@
session.crypto.warnOnUnknowDevices = NO; // Do not warn for unknown devices. We have cross-signing now
- NSMutableArray *rooms = [NSMutableArray array];
+ self.selectedRooms = [NSMutableArray array];
for (NSString *roomIdentifier in roomIdentifiers) {
MXRoom *room = [MXRoom loadRoomFromStore:self.fileStore withRoomId:roomIdentifier matrixSession:session];
if (room) {
- [rooms addObject:room];
+ [self.selectedRooms addObject:room];
}
}
- [self.shareItemSender sendItemsToRooms:rooms success:^{
+ [self.shareItemSender sendItemsToRooms:self.selectedRooms success:^{
+ self.selectedRooms = nil;
self.completionCallback(ShareManagerResultFinished);
} failure:^(NSArray *errors) {
+ self.selectedRooms = nil;
[self showFailureAlert:[VectorL10n roomEventFailedToSend]];
}];
@@ -174,6 +186,7 @@
// We consider the first enabled account.
// TODO: Handle multiple accounts
self.userAccount = [MXKAccountManager sharedManager].activeAccounts.firstObject;
+ [self checkFakeSession];
}
// Reset the file store to reload the room data.
@@ -183,12 +196,12 @@
_fileStore = nil;
}
- if (self.userAccount)
+ if (self.userAccount && fakeSession)
{
_fileStore = [[MXFileStore alloc] initWithCredentials:self.userAccount.mxCredentials];
ShareDataSource *roomDataSource = [[ShareDataSource alloc] initWithFileStore:_fileStore
- credentials:self.userAccount.mxCredentials];
+ session:fakeSession];
[self.shareViewController configureWithState:ShareViewControllerAccountStateConfigured
roomDataSource:roomDataSource];
@@ -198,6 +211,27 @@
}
}
+- (void)checkFakeSession
+{
+ if (!self.userAccount)
+ {
+ return;
+ }
+
+ if (fakeSession && [fakeSession.credentials.userId isEqualToString:self.userAccount.mxCredentials.userId])
+ {
+ return;
+ }
+
+ MXRestClient *mxRestClient = [[MXRestClient alloc] initWithCredentials:self.userAccount.mxCredentials
+ andOnUnrecognizedCertificateBlock:nil
+ andPersistentTokenDataHandler:^(void (^handler)(NSArray *credentials, void (^completion)(BOOL didUpdateCredentials))) {
+ [[MXKAccountManager sharedManager] readAndWriteCredentials:handler];
+ } andUnauthenticatedHandler:nil];
+
+ fakeSession = [[MXSession alloc] initWithMatrixRestClient:mxRestClient];
+}
+
- (void)didStartSending
{
[self.shareViewController showProgressIndicator];
diff --git a/RiotShareExtension/Sources/ShareItemSender.m b/RiotShareExtension/Sources/ShareItemSender.m
index 1758622ec..2c11e2a46 100644
--- a/RiotShareExtension/Sources/ShareItemSender.m
+++ b/RiotShareExtension/Sources/ShareItemSender.m
@@ -35,7 +35,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode)
@interface ShareItemSender ()
-@property (nonatomic, strong, readonly) UIViewController *rootViewController;
+@property (nonatomic, weak, readonly) UIViewController *rootViewController;
@property (nonatomic, strong, readonly) ShareExtensionShareItemProvider *shareItemProvider;
@property (nonatomic, strong, readonly) NSMutableArray *pendingImages;
@@ -641,7 +641,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode)
{
if (!RiotSettings.shared.showMediaCompressionPrompt)
{
- [MXSDKOptions sharedInstance].videoConversionPresetName = AVCaptureSessionPreset1920x1080;
+ [MXSDKOptions sharedInstance].videoConversionPresetName = AVAssetExportPreset1920x1080;
sendVideo();
}
else
diff --git a/RiotSwiftUI/Modules/Onboarding/UseCase/Coordinator/OnboardingUseCaseSelectionCoordinator.swift b/RiotSwiftUI/Modules/Onboarding/UseCase/Coordinator/OnboardingUseCaseSelectionCoordinator.swift
index 0a2d7eae4..e6473eeb4 100644
--- a/RiotSwiftUI/Modules/Onboarding/UseCase/Coordinator/OnboardingUseCaseSelectionCoordinator.swift
+++ b/RiotSwiftUI/Modules/Onboarding/UseCase/Coordinator/OnboardingUseCaseSelectionCoordinator.swift
@@ -41,7 +41,7 @@ final class OnboardingUseCaseSelectionCoordinator: Coordinator, Presentable {
let hostingController = VectorHostingController(rootView: view)
hostingController.vc_removeBackTitle()
- hostingController.enableNavigationBarScrollEdgesAppearance = true
+ hostingController.enableNavigationBarScrollEdgeAppearance = true
onboardingUseCaseHostingController = hostingController
}
diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/Coordinator/LocationSharingCoordinator.swift b/RiotSwiftUI/Modules/Room/LocationSharing/Coordinator/LocationSharingCoordinator.swift
index b43f4f05d..ef7c29dae 100644
--- a/RiotSwiftUI/Modules/Room/LocationSharing/Coordinator/LocationSharingCoordinator.swift
+++ b/RiotSwiftUI/Modules/Room/LocationSharing/Coordinator/LocationSharingCoordinator.swift
@@ -71,6 +71,8 @@ final class LocationSharingCoordinator: Coordinator, Presentable {
case .cancel:
self.completion?()
case .share(let latitude, let longitude):
+
+ // Show share sheet on existing location display
if let location = self.parameters.location {
self.locationSharingHostingController.present(Self.shareLocationActivityController(location), animated: true)
return
diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingModels.swift b/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingModels.swift
index a23adb6f2..cb9843a46 100644
--- a/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingModels.swift
+++ b/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingModels.swift
@@ -38,14 +38,33 @@ enum LocationSharingViewError {
@available(iOS 14, *)
struct LocationSharingViewState: BindableState {
- let mapStyleURL: URL
- let avatarData: AvatarInputProtocol
- let location: CLLocationCoordinate2D?
+ /// Map style URL
+ let mapStyleURL: URL
+
+ /// Current user avatarData
+ let userAvatarData: AvatarInputProtocol
+
+ /// User map annotation to display existing location
+ let userAnnotation: UserLocationAnnotation?
+
+ /// Map annotations to display on map
+ var annotations: [UserLocationAnnotation]
+
+ /// Map annotation to focus on
+ var highlightedAnnotation: UserLocationAnnotation?
+
var showLoadingIndicator: Bool = false
+ /// True to indicate to show and follow current user location
+ var showsUserLocation: Bool = false
+
var shareButtonVisible: Bool {
- return location == nil
+ return self.displayExistingLocation == false
+ }
+
+ var displayExistingLocation: Bool {
+ return userAnnotation != nil
}
var shareButtonEnabled: Bool {
diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingViewModel.swift b/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingViewModel.swift
index 8cbecc88a..807c5605c 100644
--- a/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingViewModel.swift
+++ b/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingViewModel.swift
@@ -36,7 +36,32 @@ class LocationSharingViewModel: LocationSharingViewModelType, LocationSharingVie
// MARK: - Setup
init(mapStyleURL: URL, avatarData: AvatarInputProtocol, location: CLLocationCoordinate2D? = nil) {
- let viewState = LocationSharingViewState(mapStyleURL: mapStyleURL, avatarData: avatarData, location: location)
+
+ var userAnnotation: UserLocationAnnotation?
+ var annotations: [UserLocationAnnotation] = []
+ var highlightedAnnotation: UserLocationAnnotation?
+ var showsUserLocation: Bool = false
+
+ // Displaying an existing location
+ if let userCoordinate = location {
+ let userLocationAnnotation = UserLocationAnnotation(avatarData: avatarData, coordinate: userCoordinate)
+
+ annotations.append(userLocationAnnotation)
+ highlightedAnnotation = userLocationAnnotation
+
+ userAnnotation = userLocationAnnotation
+ } else {
+ // Share current location
+ showsUserLocation = true
+ }
+
+ let viewState = LocationSharingViewState(mapStyleURL: mapStyleURL,
+ userAvatarData: avatarData,
+ userAnnotation: userAnnotation,
+ annotations: annotations,
+ highlightedAnnotation: highlightedAnnotation,
+ showsUserLocation: showsUserLocation)
+
super.init(initialViewState: viewState)
state.errorSubject.sink { [weak self] error in
@@ -52,11 +77,13 @@ class LocationSharingViewModel: LocationSharingViewModelType, LocationSharingVie
case .cancel:
completion?(.cancel)
case .share:
- if let location = state.location {
+ // Share existing location
+ if let location = state.userAnnotation?.coordinate {
completion?(.share(latitude: location.latitude, longitude: location.longitude))
return
}
+ // Share current user location
guard let location = state.bindings.userLocation else {
processError(.failedLocatingUser)
return
diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/Test/Unit/LocationSharingViewModelTests.swift b/RiotSwiftUI/Modules/Room/LocationSharing/Test/Unit/LocationSharingViewModelTests.swift
index a8366569b..299740112 100644
--- a/RiotSwiftUI/Modules/Room/LocationSharing/Test/Unit/LocationSharingViewModelTests.swift
+++ b/RiotSwiftUI/Modules/Room/LocationSharing/Test/Unit/LocationSharingViewModelTests.swift
@@ -33,9 +33,9 @@ class LocationSharingViewModelTests: XCTestCase {
XCTAssertFalse(viewModel.context.viewState.showLoadingIndicator)
XCTAssertNotNil(viewModel.context.viewState.mapStyleURL)
- XCTAssertNotNil(viewModel.context.viewState.avatarData)
+ XCTAssertNotNil(viewModel.context.viewState.userAvatarData)
- XCTAssertNil(viewModel.context.viewState.location)
+ XCTAssertNil(viewModel.context.viewState.userAnnotation)
XCTAssertNil(viewModel.context.viewState.bindings.userLocation)
XCTAssertNil(viewModel.context.viewState.bindings.alertInfo)
}
@@ -63,7 +63,7 @@ class LocationSharingViewModelTests: XCTestCase {
let viewModel = buildViewModel(withLocation: false)
XCTAssertNil(viewModel.context.viewState.bindings.userLocation)
- XCTAssertNil(viewModel.context.viewState.location)
+ XCTAssertNil(viewModel.context.viewState.userAnnotation)
viewModel.context.send(viewAction: .share)
@@ -79,8 +79,8 @@ class LocationSharingViewModelTests: XCTestCase {
viewModel.completion = { result in
switch result {
case .share(let latitude, let longitude):
- XCTAssertEqual(latitude, viewModel.context.viewState.location?.latitude)
- XCTAssertEqual(longitude, viewModel.context.viewState.location?.longitude)
+ XCTAssertEqual(latitude, viewModel.context.viewState.userAnnotation?.coordinate.latitude)
+ XCTAssertEqual(longitude, viewModel.context.viewState.userAnnotation?.coordinate.longitude)
expectation.fulfill()
case .cancel:
XCTFail()
@@ -88,7 +88,7 @@ class LocationSharingViewModelTests: XCTestCase {
}
XCTAssertNil(viewModel.context.viewState.bindings.userLocation)
- XCTAssertNotNil(viewModel.context.viewState.location)
+ XCTAssertNotNil(viewModel.context.viewState.userAnnotation)
viewModel.context.send(viewAction: .share)
diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/UserLocationAnnotation.swift b/RiotSwiftUI/Modules/Room/LocationSharing/UserLocationAnnotation.swift
new file mode 100644
index 000000000..f4e2453df
--- /dev/null
+++ b/RiotSwiftUI/Modules/Room/LocationSharing/UserLocationAnnotation.swift
@@ -0,0 +1,38 @@
+//
+// Copyright 2021 New Vector Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import Foundation
+import Mapbox
+
+class UserLocationAnnotation: NSObject, MGLAnnotation {
+
+ // MARK: - Properties
+
+ let avatarData: AvatarInputProtocol
+
+ let coordinate: CLLocationCoordinate2D
+
+ // MARK: - Setup
+
+ init(avatarData: AvatarInputProtocol,
+ coordinate: CLLocationCoordinate2D) {
+
+ self.coordinate = coordinate
+ self.avatarData = avatarData
+
+ super.init()
+ }
+}
diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingMapView.swift b/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingMapView.swift
index 5a782d077..0ca25dc6b 100644
--- a/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingMapView.swift
+++ b/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingMapView.swift
@@ -20,121 +20,141 @@ import Mapbox
@available(iOS 14, *)
struct LocationSharingMapView: UIViewRepresentable {
+
+ // MARK: - Constants
+
private struct Constants {
static let mapZoomLevel = 15.0
}
- let tileServerMapURL: URL
- let avatarData: AvatarInputProtocol
- let location: CLLocationCoordinate2D?
+ // MARK: - Properties
- let errorSubject: PassthroughSubject
+ /// Map style URL (https://docs.mapbox.com/api/maps/styles/)
+ let tileServerMapURL: URL
+
+ /// Map annotations
+ let annotations: [UserLocationAnnotation]
+
+ /// Map annotation to focus on
+ let highlightedAnnotation: UserLocationAnnotation?
+
+ /// Current user avatar data, used to replace current location annotation view with the user avatar
+ let userAvatarData: AvatarInputProtocol?
+
+ /// True to indicate to show and follow current user location
+ var showsUserLocation: Bool = false
+
+ /// Last user location if `showsUserLocation` has been enabled
@Binding var userLocation: CLLocationCoordinate2D?
+
+ /// Publish view errors if any
+ let errorSubject: PassthroughSubject
+
+ // MARK: - UIViewRepresentable
+
+ func makeUIView(context: Context) -> MGLMapView {
- func makeUIView(context: Context) -> some UIView {
- let mapView = MGLMapView(frame: .zero, styleURL: tileServerMapURL)
+ let mapView = self.makeMapView()
mapView.delegate = context.coordinator
-
- mapView.logoView.isHidden = true
- mapView.attributionButton.isHidden = true
-
- if let location = location {
- mapView.setCenter(location, zoomLevel: Constants.mapZoomLevel, animated: false)
-
- let pointAnnotation = MGLPointAnnotation()
- pointAnnotation.coordinate = location
- mapView.addAnnotation(pointAnnotation)
- } else {
- mapView.showsUserLocation = true
- mapView.userTrackingMode = .follow
- }
-
return mapView
}
- func updateUIView(_ uiView: UIViewType, context: Context) {
+ func updateUIView(_ mapView: MGLMapView, context: Context) {
+ mapView.vc_removeAllAnnotations()
+ mapView.addAnnotations(self.annotations)
+
+ if let highlightedAnnotation = self.highlightedAnnotation {
+ mapView.setCenter(highlightedAnnotation.coordinate, zoomLevel: Constants.mapZoomLevel, animated: false)
+ }
+
+ if self.showsUserLocation {
+ mapView.showsUserLocation = true
+ mapView.userTrackingMode = .follow
+ } else {
+ mapView.showsUserLocation = false
+ mapView.userTrackingMode = .none
+ }
}
- func makeCoordinator() -> LocationSharingMapViewCoordinator {
- LocationSharingMapViewCoordinator(avatarData: avatarData,
- errorSubject: errorSubject,
- userLocation: $userLocation)
+ func makeCoordinator() -> Coordinator {
+ Coordinator(self)
+ }
+
+ // MARK: - Private
+
+ private func makeMapView() -> MGLMapView {
+ let mapView = MGLMapView(frame: .zero, styleURL: tileServerMapURL)
+
+ mapView.logoView.isHidden = true
+ mapView.attributionButton.isHidden = true
+
+ return mapView
}
}
+// MARK: - Coordinator
@available(iOS 14, *)
-class LocationSharingMapViewCoordinator: NSObject, MGLMapViewDelegate {
+extension LocationSharingMapView {
- private let avatarData: AvatarInputProtocol
- private let errorSubject: PassthroughSubject
- @Binding private var userLocation: CLLocationCoordinate2D?
-
- init(avatarData: AvatarInputProtocol,
- errorSubject: PassthroughSubject,
- userLocation: Binding) {
- self.avatarData = avatarData
- self.errorSubject = errorSubject
- self._userLocation = userLocation
- }
-
- // MARK: - MGLMapViewDelegate
-
- func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
- return UserLocationAnnotatonView(avatarData: avatarData)
- }
-
- func mapViewDidFailLoadingMap(_ mapView: MGLMapView, withError error: Error) {
- errorSubject.send(.failedLoadingMap)
- }
-
- func mapView(_ mapView: MGLMapView, didFailToLocateUserWithError error: Error) {
- guard mapView.showsUserLocation else {
- return
+ class Coordinator: NSObject, MGLMapViewDelegate {
+
+ // MARK: - Properties
+
+ var locationSharingMapView: LocationSharingMapView
+
+ // MARK: - Setup
+
+ init(_ locationSharingMapView: LocationSharingMapView) {
+ self.locationSharingMapView = locationSharingMapView
+ }
+
+ // MARK: - MGLMapViewDelegate
+
+ func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
+
+ if let userLocationAnnotation = annotation as? UserLocationAnnotation {
+ return UserLocationAnnotatonView(userLocationAnnotation: userLocationAnnotation)
+ } else if annotation is MGLUserLocation, let currentUserAvatarData = locationSharingMapView.userAvatarData {
+ // Replace default current location annotation view with a UserLocationAnnotatonView
+ return UserLocationAnnotatonView(avatarData: currentUserAvatarData)
+ }
+
+ return nil
}
- errorSubject.send(.failedLocatingUser)
- }
-
- func mapView(_ mapView: MGLMapView, didUpdate userLocation: MGLUserLocation?) {
- self.userLocation = userLocation?.coordinate
- }
-
- func mapView(_ mapView: MGLMapView, didChangeLocationManagerAuthorization manager: MGLLocationManager) {
- guard mapView.showsUserLocation else {
- return
+ func mapViewDidFailLoadingMap(_ mapView: MGLMapView, withError error: Error) {
+ locationSharingMapView.errorSubject.send(.failedLoadingMap)
}
- switch manager.authorizationStatus {
- case .restricted:
- fallthrough
- case .denied:
- errorSubject.send(.invalidLocationAuthorization)
- default:
- break
+ func mapView(_ mapView: MGLMapView, didUpdate userLocation: MGLUserLocation?) {
+ locationSharingMapView.userLocation = userLocation?.coordinate
+ }
+
+ func mapView(_ mapView: MGLMapView, didChangeLocationManagerAuthorization manager: MGLLocationManager) {
+ guard mapView.showsUserLocation else {
+ return
+ }
+
+ switch manager.authorizationStatus {
+ case .restricted:
+ fallthrough
+ case .denied:
+ locationSharingMapView.errorSubject.send(.invalidLocationAuthorization)
+ default:
+ break
+ }
}
}
}
-@available(iOS 14, *)
-private class UserLocationAnnotatonView: MGLUserLocationAnnotationView {
+// MARK: - MGLMapView convenient methods
+extension MGLMapView {
- init(avatarData: AvatarInputProtocol) {
- super.init(frame: .zero)
-
- guard let avatarImageView = UIHostingController(rootView: LocationSharingUserMarkerView(avatarData: avatarData)).view else {
+ func vc_removeAllAnnotations() {
+ guard let annotations = self.annotations else {
return
}
-
- addSubview(avatarImageView)
-
- addConstraints([topAnchor.constraint(equalTo: avatarImageView.topAnchor),
- leadingAnchor.constraint(equalTo: avatarImageView.leadingAnchor),
- bottomAnchor.constraint(equalTo: avatarImageView.bottomAnchor),
- trailingAnchor.constraint(equalTo: avatarImageView.trailingAnchor)])
- }
-
- required init?(coder: NSCoder) {
- fatalError()
+ self.removeAnnotations(annotations)
}
}
diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingView.swift b/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingView.swift
index 61de9bfe2..317c20b02 100644
--- a/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingView.swift
+++ b/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingView.swift
@@ -34,17 +34,14 @@ struct LocationSharingView: View {
NavigationView {
ZStack(alignment: .bottom) {
LocationSharingMapView(tileServerMapURL: context.viewState.mapStyleURL,
- avatarData: context.viewState.avatarData,
- location: context.viewState.location,
- errorSubject: context.viewState.errorSubject,
- userLocation: $context.userLocation)
+ annotations: context.viewState.annotations,
+ highlightedAnnotation: context.viewState.highlightedAnnotation,
+ userAvatarData: context.viewState.userAvatarData,
+ showsUserLocation: context.viewState.showsUserLocation,
+ userLocation: $context.userLocation,
+ errorSubject: context.viewState.errorSubject)
.ignoresSafeArea()
-
- HStack {
- Link("© MapTiler", destination: URL(string: "https://www.maptiler.com/copyright/")!)
- Link("© OpenStreetMap contributors", destination: URL(string: "https://www.openstreetmap.org/copyright")!)
- }
- .font(theme.fonts.caption1)
+ MapCreditsView()
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
@@ -58,7 +55,7 @@ struct LocationSharingView: View {
.foregroundColor(theme.colors.primaryContent)
}
ToolbarItem(placement: .navigationBarTrailing) {
- if context.viewState.location != nil {
+ if context.viewState.displayExistingLocation {
Button {
context.send(viewAction: .share)
} label: {
diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/View/MapCreditsView.swift b/RiotSwiftUI/Modules/Room/LocationSharing/View/MapCreditsView.swift
new file mode 100644
index 000000000..ede4cf938
--- /dev/null
+++ b/RiotSwiftUI/Modules/Room/LocationSharing/View/MapCreditsView.swift
@@ -0,0 +1,44 @@
+//
+// Copyright 2021 New Vector Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import SwiftUI
+
+@available(iOS 14.0, *)
+struct MapCreditsView: View {
+
+ // MARK: - Properties
+
+ // MARK: Private
+
+ @Environment(\.theme) private var theme: ThemeSwiftUI
+
+ // MARK: Public
+
+ var body: some View {
+ HStack {
+ Link("© MapTiler", destination: URL(string: "https://www.maptiler.com/copyright/")!)
+ Link("© OpenStreetMap contributors", destination: URL(string: "https://www.openstreetmap.org/copyright")!)
+ }
+ .font(theme.fonts.caption1)
+ }
+}
+
+@available(iOS 14.0, *)
+struct MapCreditsView_Previews: PreviewProvider {
+ static var previews: some View {
+ MapCreditsView()
+ }
+}
diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/View/UserLocationAnnotatonView.swift b/RiotSwiftUI/Modules/Room/LocationSharing/View/UserLocationAnnotatonView.swift
new file mode 100644
index 000000000..6835ca50c
--- /dev/null
+++ b/RiotSwiftUI/Modules/Room/LocationSharing/View/UserLocationAnnotatonView.swift
@@ -0,0 +1,59 @@
+//
+// Copyright 2021 New Vector Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import Foundation
+import SwiftUI
+import Mapbox
+
+@available(iOS 14, *)
+class UserLocationAnnotatonView: MGLUserLocationAnnotationView {
+
+ // MARK: - Setup
+
+ init(avatarData: AvatarInputProtocol) {
+ super.init(frame: .zero)
+
+ self.addUserMarkerView(with: avatarData)
+ }
+
+ init(userLocationAnnotation: UserLocationAnnotation) {
+
+ // TODO: Use a reuseIdentifier
+ super.init(annotation: userLocationAnnotation, reuseIdentifier: nil)
+
+ self.addUserMarkerView(with: userLocationAnnotation.avatarData)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError()
+ }
+
+ // MARK: - Private
+
+ private func addUserMarkerView(with avatarData: AvatarInputProtocol) {
+
+ guard let avatarImageView = UIHostingController(rootView: LocationSharingUserMarkerView(avatarData: avatarData)).view else {
+ return
+ }
+
+ addSubview(avatarImageView)
+
+ addConstraints([topAnchor.constraint(equalTo: avatarImageView.topAnchor),
+ leadingAnchor.constraint(equalTo: avatarImageView.leadingAnchor),
+ bottomAnchor.constraint(equalTo: avatarImageView.bottomAnchor),
+ trailingAnchor.constraint(equalTo: avatarImageView.trailingAnchor)])
+ }
+}
diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionViewModel.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionViewModel.swift
index 51a3aa7f7..da4e3fbad 100644
--- a/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionViewModel.swift
+++ b/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionViewModel.swift
@@ -46,8 +46,8 @@ class UserSuggestionViewModel: UserSuggestionViewModelType, UserSuggestionViewMo
super.init(initialViewState: UserSuggestionViewState(items: items))
- userSuggestionService.items.sink { items in
- self.state.items = items.map({ item in
+ userSuggestionService.items.sink { [weak self] items in
+ self?.state.items = items.map({ item in
UserSuggestionViewStateItem(id: item.userId, avatar: item, displayName: item.displayName)
})
}.store(in: &cancellables)
diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/Coordinator/SpaceSettingsModalCoordinator.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/Coordinator/SpaceSettingsModalCoordinator.swift
index a9bd120c7..d1f35c377 100644
--- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/Coordinator/SpaceSettingsModalCoordinator.swift
+++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/Coordinator/SpaceSettingsModalCoordinator.swift
@@ -111,8 +111,10 @@ final class SpaceSettingsModalCoordinator: Coordinator {
private func pushOptionScreen(ofType optionType: SpaceSettingsOptionType) {
switch optionType {
case .rooms:
+ Analytics.shared.viewRoomTrigger = .spaceSettings
exploreRooms(ofSpaceWithId: self.parameters.spaceId)
case .members:
+ Analytics.shared.viewRoomTrigger = .spaceSettings
showMembers(ofSpaceWithId: self.parameters.spaceId)
case .visibility:
showAccess(ofSpaceWithId: self.parameters.spaceId)
diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Coordinator/SpaceSettingsCoordinator.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Coordinator/SpaceSettingsCoordinator.swift
index 0199bb8a5..d0f4c6790 100644
--- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Coordinator/SpaceSettingsCoordinator.swift
+++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Coordinator/SpaceSettingsCoordinator.swift
@@ -53,7 +53,7 @@ final class SpaceSettingsCoordinator: Coordinator, Presentable {
.addDependency(AvatarService.instantiate(mediaManager: parameters.session.mediaManager))
spaceSettingsViewModel = viewModel
let controller = VectorHostingController(rootView: view)
- controller.enableNavigationBarScrollEdgesAppearance = true
+ controller.enableNavigationBarScrollEdgeAppearance = true
spaceSettingsHostingController = controller
}
diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/MatrixSDK/SpaceSettingsService.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/MatrixSDK/SpaceSettingsService.swift
index bec782b3a..429f2e5a5 100644
--- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/MatrixSDK/SpaceSettingsService.swift
+++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/MatrixSDK/SpaceSettingsService.swift
@@ -114,6 +114,10 @@ class SpaceSettingsService: SpaceSettingsServiceProtocol {
userDefinedAddress = newValue
}
+ func trackSpace() {
+ Analytics.shared.exploringSpace = session.spaceService.getSpace(withId: spaceId)
+ }
+
// MARK: - Private
private func readRoomState() {
diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/Mock/MockSpaceSettingsService.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/Mock/MockSpaceSettingsService.swift
index 1ddf6ea08..d58e980ad 100644
--- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/Mock/MockSpaceSettingsService.swift
+++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/Mock/MockSpaceSettingsService.swift
@@ -53,4 +53,8 @@ class MockSpaceSettingsService: SpaceSettingsServiceProtocol {
func simulateUpdate(addressValidationStatus: SpaceCreationSettingsAddressValidationStatus) {
self.addressValidationSubject.value = addressValidationStatus
}
+
+ func trackSpace() {
+
+ }
}
diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/SpaceSettingsServiceProtocol.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/SpaceSettingsServiceProtocol.swift
index 51775c8d6..3d0666047 100644
--- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/SpaceSettingsServiceProtocol.swift
+++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/SpaceSettingsServiceProtocol.swift
@@ -34,6 +34,7 @@ protocol SpaceSettingsServiceProtocol: Avatarable {
func update(roomName: String, topic: String, address: String, avatar: UIImage?, completion: ((_ result: SpaceSettingsServiceCompletionResult) -> Void)?)
func addressDidChange(_ newValue: String)
+ func trackSpace()
}
// MARK: Avatarable
diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsModels.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsModels.swift
index 86e606c0e..6ca32eef4 100644
--- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsModels.swift
+++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsModels.swift
@@ -120,4 +120,5 @@ enum SpaceSettingsViewAction {
case pickImage(_ sourceRect: CGRect)
case optionSelected(_ optionType: SpaceSettingsOptionType)
case addressChanged(_ newValue: String)
+ case trackSpace
}
diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsViewModel.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsViewModel.swift
index fc2771823..f2969bb86 100644
--- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsViewModel.swift
+++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsViewModel.swift
@@ -123,6 +123,8 @@ class SpaceSettingsViewModel: SpaceSettingsViewModelType, SpaceSettingsViewModel
}
case .addressChanged(let newValue):
service.addressDidChange(newValue)
+ case .trackSpace:
+ service.trackSpace()
}
}
diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/View/SpaceSettings.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/View/SpaceSettings.swift
index c026ae358..9ad8bca8f 100644
--- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/View/SpaceSettings.swift
+++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/View/SpaceSettings.swift
@@ -68,6 +68,9 @@ struct SpaceSettings: View {
}),
secondaryButton: .cancel())
})
+ .onAppear {
+ viewModel.send(viewAction: .trackSpace)
+ }
}
// MARK: - Private
diff --git a/SiriIntents/IntentHandler.m b/SiriIntents/IntentHandler.m
index 8220d9fed..94d120a4f 100644
--- a/SiriIntents/IntentHandler.m
+++ b/SiriIntents/IntentHandler.m
@@ -27,6 +27,12 @@
// Build Settings
@property (nonatomic) id configuration;
+/**
+ The room that is currently being used to send a message. This is to ensure a
+ strong ref is maintained on the `MXRoom` until sending has completed.
+ */
+@property (nonatomic) MXRoom *selectedRoom;
+
@end
@implementation IntentHandler
@@ -242,17 +248,22 @@
[session setStore:fileStore success:^{
MXStrongifyAndReturnIfNil(session);
- MXRoom *room = [MXRoom loadRoomFromStore:fileStore withRoomId:roomID matrixSession:session];
+ self.selectedRoom = [MXRoom loadRoomFromStore:fileStore withRoomId:roomID matrixSession:session];
// Do not warn for unknown devices. We have cross-signing now
session.crypto.warnOnUnknowDevices = NO;
- [room sendTextMessage:intent.content
- threadId:nil
- success:^(NSString *eventId) {
+ MXWeakify(self);
+ [self.selectedRoom sendTextMessage:intent.content
+ threadId:nil
+ success:^(NSString *eventId) {
completeWithCode(INSendMessageIntentResponseCodeSuccess);
+ MXStrongifyAndReturnIfNil(self);
+ self.selectedRoom = nil;
} failure:^(NSError *error) {
completeWithCode(INSendMessageIntentResponseCodeFailure);
+ MXStrongifyAndReturnIfNil(self);
+ self.selectedRoom = nil;
}];
} failure:^(NSError *error) {
diff --git a/changelog.d/5058.bugfix b/changelog.d/5058.bugfix
new file mode 100644
index 000000000..c1bba1101
--- /dev/null
+++ b/changelog.d/5058.bugfix
@@ -0,0 +1 @@
+UserSuggestionViewModel: Fix retain cycle
diff --git a/changelog.d/5401.change b/changelog.d/5401.change
new file mode 100644
index 000000000..141708597
--- /dev/null
+++ b/changelog.d/5401.change
@@ -0,0 +1 @@
+Instrument metrics for the IA project.
\ No newline at end of file
diff --git a/changelog.d/5441.change b/changelog.d/5441.change
new file mode 100644
index 000000000..ba20e8807
--- /dev/null
+++ b/changelog.d/5441.change
@@ -0,0 +1 @@
+RoomDataSource: Reload thread screen for the first message.
diff --git a/changelog.d/5500.change b/changelog.d/5500.change
new file mode 100644
index 000000000..bcfe8ba8a
--- /dev/null
+++ b/changelog.d/5500.change
@@ -0,0 +1 @@
+Change behaviour of avatar/self in left menu to match common paradigm and take user to their own profile/settings
\ No newline at end of file
diff --git a/changelog.d/5547.bugfix b/changelog.d/5547.bugfix
new file mode 100644
index 000000000..570df2392
--- /dev/null
+++ b/changelog.d/5547.bugfix
@@ -0,0 +1 @@
+Home: Fix crash when pressing tabs
diff --git a/changelog.d/5769.change b/changelog.d/5769.change
new file mode 100644
index 000000000..d561dabb1
--- /dev/null
+++ b/changelog.d/5769.change
@@ -0,0 +1 @@
+IA Metrics: added trigger to JoinedRoom event and implemented ViewRoom event
\ No newline at end of file
diff --git a/changelog.d/5805.bugfix b/changelog.d/5805.bugfix
new file mode 100644
index 000000000..831afcca9
--- /dev/null
+++ b/changelog.d/5805.bugfix
@@ -0,0 +1 @@
+Share Extension: Stop logging crashes due to intentional exception that frees up memory and handle changes to MXRoom in the SDK.
diff --git a/changelog.d/5825.bugfix b/changelog.d/5825.bugfix
new file mode 100644
index 000000000..dd5050379
--- /dev/null
+++ b/changelog.d/5825.bugfix
@@ -0,0 +1 @@
+Crash after leaving last space.
\ No newline at end of file
diff --git a/changelog.d/5827.change b/changelog.d/5827.change
new file mode 100644
index 000000000..e628ceda6
--- /dev/null
+++ b/changelog.d/5827.change
@@ -0,0 +1 @@
+Location sharing: Support multiple user annotation views on the map.
diff --git a/changelog.d/5829.change b/changelog.d/5829.change
new file mode 100644
index 000000000..8d936b8ad
--- /dev/null
+++ b/changelog.d/5829.change
@@ -0,0 +1 @@
+MXKRoomDataSource: Pass threadId of room data source for replies.
diff --git a/changelog.d/5841.change b/changelog.d/5841.change
new file mode 100644
index 000000000..a49b81fe7
--- /dev/null
+++ b/changelog.d/5841.change
@@ -0,0 +1 @@
+MXKEventFormatter: Fix edit fallback usage for edited events.
diff --git a/changelog.d/5853.change b/changelog.d/5853.change
new file mode 100644
index 000000000..c35b4255a
--- /dev/null
+++ b/changelog.d/5853.change
@@ -0,0 +1 @@
+RoomViewController: Remove thread list bar button item badge count.
diff --git a/changelog.d/5857.wip b/changelog.d/5857.wip
new file mode 100644
index 000000000..80369f90c
--- /dev/null
+++ b/changelog.d/5857.wip
@@ -0,0 +1 @@
+Location sharing: Handle live location banner view in room screen.
\ No newline at end of file
diff --git a/changelog.d/pr-5826.api b/changelog.d/pr-5826.api
new file mode 100644
index 000000000..e4847b927
--- /dev/null
+++ b/changelog.d/pr-5826.api
@@ -0,0 +1 @@
+ Rename scrollEdgesAppearance → scrollEdgeAppearance to match UIKit.
\ No newline at end of file