Make AnalyticsService mockable via `AnalyticsServiceProtocol (#5599)

* Create AnalyticsServiceProtocol and conform AnalyticsService

* Replace AnalyticsService properties and arguments with AnalyticsServiceProtocol

* Inline AnalyticsServiceMock replacing old mock

* handle more complex AnalyticsServiceMock callsites

* move documentation from AnalyticsService to AnalyticsServiceProtocol
This commit is contained in:
Michael
2026-05-20 00:23:58 -05:00
committed by GitHub
parent 946cb53559
commit 5a07c98afc
118 changed files with 1218 additions and 505 deletions
@@ -22,7 +22,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
private let targetConfiguration: Target.ConfigurationResult
private let appMediator: AppMediator
private let appSettings: AppSettings
private let analyticsService: AnalyticsService
private let analyticsService: AnalyticsServiceProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let appDelegate: AppDelegate
@@ -946,7 +946,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
}
}
private static func setupSentry(bugReportService: BugReportServiceProtocol, appSettings: AppSettings, analytics: AnalyticsService) {
private static func setupSentry(bugReportService: BugReportServiceProtocol, appSettings: AppSettings, analytics: AnalyticsServiceProtocol) {
guard let bugReportSentryURL = appSettings.bugReportSentryURL else { return }
let options: Options = .init()
@@ -36,7 +36,7 @@ struct CommonFlowParameters {
let appMediator: AppMediatorProtocol
let appSettings: AppSettings
let appHooks: AppHooks
let analytics: AnalyticsService
let analytics: AnalyticsServiceProtocol
let userIndicatorController: UserIndicatorControllerProtocol
let notificationManager: NotificationManagerProtocol
let stateMachineFactory: StateMachineFactoryProtocol
@@ -23,7 +23,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
private let appMediator: AppMediatorProtocol
private let appSettings: AppSettings
private let appHooks: AppHooks
private let analytics: AnalyticsService
private let analytics: AnalyticsServiceProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
enum State: StateType {
@@ -109,7 +109,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
appMediator: AppMediatorProtocol,
appSettings: AppSettings,
appHooks: AppHooks,
analytics: AnalyticsService,
analytics: AnalyticsServiceProtocol,
userIndicatorController: UserIndicatorControllerProtocol) {
self.authenticationService = authenticationService
self.bugReportService = bugReportService
@@ -19,7 +19,7 @@ enum OnboardingFlowCoordinatorAction {
class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
private let userSession: UserSessionProtocol
private let appLockService: AppLockServiceProtocol
private let analyticsService: AnalyticsService
private let analyticsService: AnalyticsServiceProtocol
private let appMediator: AppMediatorProtocol
private let appSettings: AppSettings
private let appHooks: AppHooks
@@ -20,7 +20,7 @@ struct RoomRolesAndPermissionsFlowCoordinatorParameters {
let mediaProvider: MediaProviderProtocol
let navigationStackCoordinator: NavigationStackCoordinator
let userIndicatorController: UserIndicatorControllerProtocol
let analytics: AnalyticsService
let analytics: AnalyticsServiceProtocol
}
class RoomRolesAndPermissionsFlowCoordinator: FlowCoordinatorProtocol {
@@ -28,7 +28,7 @@ class RoomRolesAndPermissionsFlowCoordinator: FlowCoordinatorProtocol {
private let navigationStackCoordinator: NavigationStackCoordinator
private let mediaProvider: MediaProviderProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let analytics: AnalyticsService
private let analytics: AnalyticsServiceProtocol
enum State: StateType {
/// The state machine hasn't started.
@@ -1,21 +0,0 @@
//
// Copyright 2026 Element Creations Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
extension AnalyticsService {
static func mock(_ clientOverride: AnalyticsClientProtocol? = nil, settings: AppSettings? = nil) -> AnalyticsService {
let client: AnalyticsClientProtocol
if let clientOverride {
client = clientOverride
} else {
let mockClient = AnalyticsClientMock()
mockClient.isRunning = false
client = mockClient
}
return .init(client: client, appSettings: settings ?? AppSettings())
}
}
@@ -0,0 +1,16 @@
//
// Copyright 2026 Element Creations Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
extension AnalyticsServiceMock {
static func `default`() -> AnalyticsServiceMock {
let mock = AnalyticsServiceMock()
mock.isEnabled = false
mock.shouldShowAnalyticsPrompt = false
mock.signpost = Signposter()
return mock
}
}
@@ -256,6 +256,761 @@ class AnalyticsClientMock: AnalyticsClientProtocol, @unchecked Sendable {
updateUserPropertiesClosure?(event)
}
}
class AnalyticsServiceMock: AnalyticsServiceProtocol, @unchecked Sendable {
var signpost: Signposter {
get { return underlyingSignpost }
set(value) { underlyingSignpost = value }
}
var underlyingSignpost: Signposter!
var shouldShowAnalyticsPrompt: Bool {
get { return underlyingShouldShowAnalyticsPrompt }
set(value) { underlyingShouldShowAnalyticsPrompt = value }
}
var underlyingShouldShowAnalyticsPrompt: Bool!
var isEnabled: Bool {
get { return underlyingIsEnabled }
set(value) { underlyingIsEnabled = value }
}
var underlyingIsEnabled: Bool!
//MARK: - optIn
var optInUnderlyingCallsCount = 0
var optInCallsCount: Int {
get {
if Thread.isMainThread {
return optInUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = optInUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
optInUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
optInUnderlyingCallsCount = newValue
}
}
}
}
var optInCalled: Bool {
return optInCallsCount > 0
}
var optInClosure: (() -> Void)?
func optIn() {
optInCallsCount += 1
optInClosure?()
}
//MARK: - optOut
var optOutUnderlyingCallsCount = 0
var optOutCallsCount: Int {
get {
if Thread.isMainThread {
return optOutUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = optOutUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
optOutUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
optOutUnderlyingCallsCount = newValue
}
}
}
}
var optOutCalled: Bool {
return optOutCallsCount > 0
}
var optOutClosure: (() -> Void)?
func optOut() {
optOutCallsCount += 1
optOutClosure?()
}
//MARK: - startIfEnabled
var startIfEnabledUnderlyingCallsCount = 0
var startIfEnabledCallsCount: Int {
get {
if Thread.isMainThread {
return startIfEnabledUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = startIfEnabledUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
startIfEnabledUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
startIfEnabledUnderlyingCallsCount = newValue
}
}
}
}
var startIfEnabledCalled: Bool {
return startIfEnabledCallsCount > 0
}
var startIfEnabledClosure: (() -> Void)?
func startIfEnabled() {
startIfEnabledCallsCount += 1
startIfEnabledClosure?()
}
//MARK: - reset
var resetUnderlyingCallsCount = 0
var resetCallsCount: Int {
get {
if Thread.isMainThread {
return resetUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = resetUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
resetUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
resetUnderlyingCallsCount = newValue
}
}
}
}
var resetCalled: Bool {
return resetCallsCount > 0
}
var resetClosure: (() -> Void)?
func reset() {
resetCallsCount += 1
resetClosure?()
}
//MARK: - resetConsentState
var resetConsentStateUnderlyingCallsCount = 0
var resetConsentStateCallsCount: Int {
get {
if Thread.isMainThread {
return resetConsentStateUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = resetConsentStateUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
resetConsentStateUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
resetConsentStateUnderlyingCallsCount = newValue
}
}
}
}
var resetConsentStateCalled: Bool {
return resetConsentStateCallsCount > 0
}
var resetConsentStateClosure: (() -> Void)?
func resetConsentState() {
resetConsentStateCallsCount += 1
resetConsentStateClosure?()
}
//MARK: - track
var trackScreenDurationUnderlyingCallsCount = 0
var trackScreenDurationCallsCount: Int {
get {
if Thread.isMainThread {
return trackScreenDurationUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = trackScreenDurationUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
trackScreenDurationUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
trackScreenDurationUnderlyingCallsCount = newValue
}
}
}
}
var trackScreenDurationCalled: Bool {
return trackScreenDurationCallsCount > 0
}
var trackScreenDurationReceivedArguments: (screen: AnalyticsEvent.MobileScreen.ScreenName, milliseconds: Int?)?
var trackScreenDurationReceivedInvocations: [(screen: AnalyticsEvent.MobileScreen.ScreenName, milliseconds: Int?)] = []
var trackScreenDurationClosure: ((AnalyticsEvent.MobileScreen.ScreenName, Int?) -> Void)?
func track(screen: AnalyticsEvent.MobileScreen.ScreenName, duration milliseconds: Int?) {
trackScreenDurationCallsCount += 1
trackScreenDurationReceivedArguments = (screen: screen, milliseconds: milliseconds)
DispatchQueue.main.async {
self.trackScreenDurationReceivedInvocations.append((screen: screen, milliseconds: milliseconds))
}
trackScreenDurationClosure?(screen, milliseconds)
}
//MARK: - trackInteraction
var trackInteractionIndexNameUnderlyingCallsCount = 0
var trackInteractionIndexNameCallsCount: Int {
get {
if Thread.isMainThread {
return trackInteractionIndexNameUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = trackInteractionIndexNameUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
trackInteractionIndexNameUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
trackInteractionIndexNameUnderlyingCallsCount = newValue
}
}
}
}
var trackInteractionIndexNameCalled: Bool {
return trackInteractionIndexNameCallsCount > 0
}
var trackInteractionIndexNameReceivedArguments: (index: Int?, name: AnalyticsEvent.Interaction.Name)?
var trackInteractionIndexNameReceivedInvocations: [(index: Int?, name: AnalyticsEvent.Interaction.Name)] = []
var trackInteractionIndexNameClosure: ((Int?, AnalyticsEvent.Interaction.Name) -> Void)?
func trackInteraction(index: Int?, name: AnalyticsEvent.Interaction.Name) {
trackInteractionIndexNameCallsCount += 1
trackInteractionIndexNameReceivedArguments = (index: index, name: name)
DispatchQueue.main.async {
self.trackInteractionIndexNameReceivedInvocations.append((index: index, name: name))
}
trackInteractionIndexNameClosure?(index, name)
}
//MARK: - trackError
var trackErrorContextDomainNameTimeToDecryptMillisEventLocalAgeMillisIsFederatedIsMatrixDotOrgUserTrustsOwnIdentityWasVisibleToUserUnderlyingCallsCount = 0
var trackErrorContextDomainNameTimeToDecryptMillisEventLocalAgeMillisIsFederatedIsMatrixDotOrgUserTrustsOwnIdentityWasVisibleToUserCallsCount: Int {
get {
if Thread.isMainThread {
return trackErrorContextDomainNameTimeToDecryptMillisEventLocalAgeMillisIsFederatedIsMatrixDotOrgUserTrustsOwnIdentityWasVisibleToUserUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = trackErrorContextDomainNameTimeToDecryptMillisEventLocalAgeMillisIsFederatedIsMatrixDotOrgUserTrustsOwnIdentityWasVisibleToUserUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
trackErrorContextDomainNameTimeToDecryptMillisEventLocalAgeMillisIsFederatedIsMatrixDotOrgUserTrustsOwnIdentityWasVisibleToUserUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
trackErrorContextDomainNameTimeToDecryptMillisEventLocalAgeMillisIsFederatedIsMatrixDotOrgUserTrustsOwnIdentityWasVisibleToUserUnderlyingCallsCount = newValue
}
}
}
}
var trackErrorContextDomainNameTimeToDecryptMillisEventLocalAgeMillisIsFederatedIsMatrixDotOrgUserTrustsOwnIdentityWasVisibleToUserCalled: Bool {
return trackErrorContextDomainNameTimeToDecryptMillisEventLocalAgeMillisIsFederatedIsMatrixDotOrgUserTrustsOwnIdentityWasVisibleToUserCallsCount > 0
}
var trackErrorContextDomainNameTimeToDecryptMillisEventLocalAgeMillisIsFederatedIsMatrixDotOrgUserTrustsOwnIdentityWasVisibleToUserReceivedArguments: (context: String?, domain: AnalyticsEvent.Error.Domain, name: AnalyticsEvent.Error.Name, timeToDecryptMillis: Int?, eventLocalAgeMillis: Int?, isFederated: Bool?, isMatrixDotOrg: Bool?, userTrustsOwnIdentity: Bool?, wasVisibleToUser: Bool?)?
var trackErrorContextDomainNameTimeToDecryptMillisEventLocalAgeMillisIsFederatedIsMatrixDotOrgUserTrustsOwnIdentityWasVisibleToUserReceivedInvocations: [(context: String?, domain: AnalyticsEvent.Error.Domain, name: AnalyticsEvent.Error.Name, timeToDecryptMillis: Int?, eventLocalAgeMillis: Int?, isFederated: Bool?, isMatrixDotOrg: Bool?, userTrustsOwnIdentity: Bool?, wasVisibleToUser: Bool?)] = []
var trackErrorContextDomainNameTimeToDecryptMillisEventLocalAgeMillisIsFederatedIsMatrixDotOrgUserTrustsOwnIdentityWasVisibleToUserClosure: ((String?, AnalyticsEvent.Error.Domain, AnalyticsEvent.Error.Name, Int?, Int?, Bool?, Bool?, Bool?, Bool?) -> Void)?
func trackError(context: String?, domain: AnalyticsEvent.Error.Domain, name: AnalyticsEvent.Error.Name, timeToDecryptMillis: Int?, eventLocalAgeMillis: Int?, isFederated: Bool?, isMatrixDotOrg: Bool?, userTrustsOwnIdentity: Bool?, wasVisibleToUser: Bool?) {
trackErrorContextDomainNameTimeToDecryptMillisEventLocalAgeMillisIsFederatedIsMatrixDotOrgUserTrustsOwnIdentityWasVisibleToUserCallsCount += 1
trackErrorContextDomainNameTimeToDecryptMillisEventLocalAgeMillisIsFederatedIsMatrixDotOrgUserTrustsOwnIdentityWasVisibleToUserReceivedArguments = (context: context, domain: domain, name: name, timeToDecryptMillis: timeToDecryptMillis, eventLocalAgeMillis: eventLocalAgeMillis, isFederated: isFederated, isMatrixDotOrg: isMatrixDotOrg, userTrustsOwnIdentity: userTrustsOwnIdentity, wasVisibleToUser: wasVisibleToUser)
DispatchQueue.main.async {
self.trackErrorContextDomainNameTimeToDecryptMillisEventLocalAgeMillisIsFederatedIsMatrixDotOrgUserTrustsOwnIdentityWasVisibleToUserReceivedInvocations.append((context: context, domain: domain, name: name, timeToDecryptMillis: timeToDecryptMillis, eventLocalAgeMillis: eventLocalAgeMillis, isFederated: isFederated, isMatrixDotOrg: isMatrixDotOrg, userTrustsOwnIdentity: userTrustsOwnIdentity, wasVisibleToUser: wasVisibleToUser))
}
trackErrorContextDomainNameTimeToDecryptMillisEventLocalAgeMillisIsFederatedIsMatrixDotOrgUserTrustsOwnIdentityWasVisibleToUserClosure?(context, domain, name, timeToDecryptMillis, eventLocalAgeMillis, isFederated, isMatrixDotOrg, userTrustsOwnIdentity, wasVisibleToUser)
}
//MARK: - trackCreatedRoom
var trackCreatedRoomIsDMUnderlyingCallsCount = 0
var trackCreatedRoomIsDMCallsCount: Int {
get {
if Thread.isMainThread {
return trackCreatedRoomIsDMUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = trackCreatedRoomIsDMUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
trackCreatedRoomIsDMUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
trackCreatedRoomIsDMUnderlyingCallsCount = newValue
}
}
}
}
var trackCreatedRoomIsDMCalled: Bool {
return trackCreatedRoomIsDMCallsCount > 0
}
var trackCreatedRoomIsDMReceivedIsDM: Bool?
var trackCreatedRoomIsDMReceivedInvocations: [Bool] = []
var trackCreatedRoomIsDMClosure: ((Bool) -> Void)?
func trackCreatedRoom(isDM: Bool) {
trackCreatedRoomIsDMCallsCount += 1
trackCreatedRoomIsDMReceivedIsDM = isDM
DispatchQueue.main.async {
self.trackCreatedRoomIsDMReceivedInvocations.append(isDM)
}
trackCreatedRoomIsDMClosure?(isDM)
}
//MARK: - trackComposer
var trackComposerInThreadIsEditingIsReplyMessageTypeStartsThreadUnderlyingCallsCount = 0
var trackComposerInThreadIsEditingIsReplyMessageTypeStartsThreadCallsCount: Int {
get {
if Thread.isMainThread {
return trackComposerInThreadIsEditingIsReplyMessageTypeStartsThreadUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = trackComposerInThreadIsEditingIsReplyMessageTypeStartsThreadUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
trackComposerInThreadIsEditingIsReplyMessageTypeStartsThreadUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
trackComposerInThreadIsEditingIsReplyMessageTypeStartsThreadUnderlyingCallsCount = newValue
}
}
}
}
var trackComposerInThreadIsEditingIsReplyMessageTypeStartsThreadCalled: Bool {
return trackComposerInThreadIsEditingIsReplyMessageTypeStartsThreadCallsCount > 0
}
var trackComposerInThreadIsEditingIsReplyMessageTypeStartsThreadReceivedArguments: (inThread: Bool, isEditing: Bool, isReply: Bool, messageType: AnalyticsEvent.Composer.MessageType, startsThread: Bool?)?
var trackComposerInThreadIsEditingIsReplyMessageTypeStartsThreadReceivedInvocations: [(inThread: Bool, isEditing: Bool, isReply: Bool, messageType: AnalyticsEvent.Composer.MessageType, startsThread: Bool?)] = []
var trackComposerInThreadIsEditingIsReplyMessageTypeStartsThreadClosure: ((Bool, Bool, Bool, AnalyticsEvent.Composer.MessageType, Bool?) -> Void)?
func trackComposer(inThread: Bool, isEditing: Bool, isReply: Bool, messageType: AnalyticsEvent.Composer.MessageType, startsThread: Bool?) {
trackComposerInThreadIsEditingIsReplyMessageTypeStartsThreadCallsCount += 1
trackComposerInThreadIsEditingIsReplyMessageTypeStartsThreadReceivedArguments = (inThread: inThread, isEditing: isEditing, isReply: isReply, messageType: messageType, startsThread: startsThread)
DispatchQueue.main.async {
self.trackComposerInThreadIsEditingIsReplyMessageTypeStartsThreadReceivedInvocations.append((inThread: inThread, isEditing: isEditing, isReply: isReply, messageType: messageType, startsThread: startsThread))
}
trackComposerInThreadIsEditingIsReplyMessageTypeStartsThreadClosure?(inThread, isEditing, isReply, messageType, startsThread)
}
//MARK: - trackViewRoom
var trackViewRoomIsDMIsSpaceUnderlyingCallsCount = 0
var trackViewRoomIsDMIsSpaceCallsCount: Int {
get {
if Thread.isMainThread {
return trackViewRoomIsDMIsSpaceUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = trackViewRoomIsDMIsSpaceUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
trackViewRoomIsDMIsSpaceUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
trackViewRoomIsDMIsSpaceUnderlyingCallsCount = newValue
}
}
}
}
var trackViewRoomIsDMIsSpaceCalled: Bool {
return trackViewRoomIsDMIsSpaceCallsCount > 0
}
var trackViewRoomIsDMIsSpaceReceivedArguments: (isDM: Bool, isSpace: Bool)?
var trackViewRoomIsDMIsSpaceReceivedInvocations: [(isDM: Bool, isSpace: Bool)] = []
var trackViewRoomIsDMIsSpaceClosure: ((Bool, Bool) -> Void)?
func trackViewRoom(isDM: Bool, isSpace: Bool) {
trackViewRoomIsDMIsSpaceCallsCount += 1
trackViewRoomIsDMIsSpaceReceivedArguments = (isDM: isDM, isSpace: isSpace)
DispatchQueue.main.async {
self.trackViewRoomIsDMIsSpaceReceivedInvocations.append((isDM: isDM, isSpace: isSpace))
}
trackViewRoomIsDMIsSpaceClosure?(isDM, isSpace)
}
//MARK: - trackJoinedRoom
var trackJoinedRoomIsDMIsSpaceActiveMemberCountUnderlyingCallsCount = 0
var trackJoinedRoomIsDMIsSpaceActiveMemberCountCallsCount: Int {
get {
if Thread.isMainThread {
return trackJoinedRoomIsDMIsSpaceActiveMemberCountUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = trackJoinedRoomIsDMIsSpaceActiveMemberCountUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
trackJoinedRoomIsDMIsSpaceActiveMemberCountUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
trackJoinedRoomIsDMIsSpaceActiveMemberCountUnderlyingCallsCount = newValue
}
}
}
}
var trackJoinedRoomIsDMIsSpaceActiveMemberCountCalled: Bool {
return trackJoinedRoomIsDMIsSpaceActiveMemberCountCallsCount > 0
}
var trackJoinedRoomIsDMIsSpaceActiveMemberCountReceivedArguments: (isDM: Bool, isSpace: Bool, activeMemberCount: UInt)?
var trackJoinedRoomIsDMIsSpaceActiveMemberCountReceivedInvocations: [(isDM: Bool, isSpace: Bool, activeMemberCount: UInt)] = []
var trackJoinedRoomIsDMIsSpaceActiveMemberCountClosure: ((Bool, Bool, UInt) -> Void)?
func trackJoinedRoom(isDM: Bool, isSpace: Bool, activeMemberCount: UInt) {
trackJoinedRoomIsDMIsSpaceActiveMemberCountCallsCount += 1
trackJoinedRoomIsDMIsSpaceActiveMemberCountReceivedArguments = (isDM: isDM, isSpace: isSpace, activeMemberCount: activeMemberCount)
DispatchQueue.main.async {
self.trackJoinedRoomIsDMIsSpaceActiveMemberCountReceivedInvocations.append((isDM: isDM, isSpace: isSpace, activeMemberCount: activeMemberCount))
}
trackJoinedRoomIsDMIsSpaceActiveMemberCountClosure?(isDM, isSpace, activeMemberCount)
}
//MARK: - trackPollCreated
var trackPollCreatedIsUndisclosedNumberOfAnswersUnderlyingCallsCount = 0
var trackPollCreatedIsUndisclosedNumberOfAnswersCallsCount: Int {
get {
if Thread.isMainThread {
return trackPollCreatedIsUndisclosedNumberOfAnswersUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = trackPollCreatedIsUndisclosedNumberOfAnswersUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
trackPollCreatedIsUndisclosedNumberOfAnswersUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
trackPollCreatedIsUndisclosedNumberOfAnswersUnderlyingCallsCount = newValue
}
}
}
}
var trackPollCreatedIsUndisclosedNumberOfAnswersCalled: Bool {
return trackPollCreatedIsUndisclosedNumberOfAnswersCallsCount > 0
}
var trackPollCreatedIsUndisclosedNumberOfAnswersReceivedArguments: (isUndisclosed: Bool, numberOfAnswers: Int)?
var trackPollCreatedIsUndisclosedNumberOfAnswersReceivedInvocations: [(isUndisclosed: Bool, numberOfAnswers: Int)] = []
var trackPollCreatedIsUndisclosedNumberOfAnswersClosure: ((Bool, Int) -> Void)?
func trackPollCreated(isUndisclosed: Bool, numberOfAnswers: Int) {
trackPollCreatedIsUndisclosedNumberOfAnswersCallsCount += 1
trackPollCreatedIsUndisclosedNumberOfAnswersReceivedArguments = (isUndisclosed: isUndisclosed, numberOfAnswers: numberOfAnswers)
DispatchQueue.main.async {
self.trackPollCreatedIsUndisclosedNumberOfAnswersReceivedInvocations.append((isUndisclosed: isUndisclosed, numberOfAnswers: numberOfAnswers))
}
trackPollCreatedIsUndisclosedNumberOfAnswersClosure?(isUndisclosed, numberOfAnswers)
}
//MARK: - trackPollVote
var trackPollVoteUnderlyingCallsCount = 0
var trackPollVoteCallsCount: Int {
get {
if Thread.isMainThread {
return trackPollVoteUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = trackPollVoteUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
trackPollVoteUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
trackPollVoteUnderlyingCallsCount = newValue
}
}
}
}
var trackPollVoteCalled: Bool {
return trackPollVoteCallsCount > 0
}
var trackPollVoteClosure: (() -> Void)?
func trackPollVote() {
trackPollVoteCallsCount += 1
trackPollVoteClosure?()
}
//MARK: - trackPollEnd
var trackPollEndUnderlyingCallsCount = 0
var trackPollEndCallsCount: Int {
get {
if Thread.isMainThread {
return trackPollEndUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = trackPollEndUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
trackPollEndUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
trackPollEndUnderlyingCallsCount = newValue
}
}
}
}
var trackPollEndCalled: Bool {
return trackPollEndCallsCount > 0
}
var trackPollEndClosure: (() -> Void)?
func trackPollEnd() {
trackPollEndCallsCount += 1
trackPollEndClosure?()
}
//MARK: - trackRoomModeration
var trackRoomModerationActionRoleUnderlyingCallsCount = 0
var trackRoomModerationActionRoleCallsCount: Int {
get {
if Thread.isMainThread {
return trackRoomModerationActionRoleUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = trackRoomModerationActionRoleUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
trackRoomModerationActionRoleUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
trackRoomModerationActionRoleUnderlyingCallsCount = newValue
}
}
}
}
var trackRoomModerationActionRoleCalled: Bool {
return trackRoomModerationActionRoleCallsCount > 0
}
var trackRoomModerationActionRoleReceivedArguments: (action: AnalyticsEvent.RoomModeration.Action, role: RoomRole?)?
var trackRoomModerationActionRoleReceivedInvocations: [(action: AnalyticsEvent.RoomModeration.Action, role: RoomRole?)] = []
var trackRoomModerationActionRoleClosure: ((AnalyticsEvent.RoomModeration.Action, RoomRole?) -> Void)?
func trackRoomModeration(action: AnalyticsEvent.RoomModeration.Action, role: RoomRole?) {
trackRoomModerationActionRoleCallsCount += 1
trackRoomModerationActionRoleReceivedArguments = (action: action, role: role)
DispatchQueue.main.async {
self.trackRoomModerationActionRoleReceivedInvocations.append((action: action, role: role))
}
trackRoomModerationActionRoleClosure?(action, role)
}
//MARK: - trackSessionSecurityState
var trackSessionSecurityStateUnderlyingCallsCount = 0
var trackSessionSecurityStateCallsCount: Int {
get {
if Thread.isMainThread {
return trackSessionSecurityStateUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = trackSessionSecurityStateUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
trackSessionSecurityStateUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
trackSessionSecurityStateUnderlyingCallsCount = newValue
}
}
}
}
var trackSessionSecurityStateCalled: Bool {
return trackSessionSecurityStateCallsCount > 0
}
var trackSessionSecurityStateReceivedState: SessionSecurityState?
var trackSessionSecurityStateReceivedInvocations: [SessionSecurityState] = []
var trackSessionSecurityStateClosure: ((SessionSecurityState) -> Void)?
func trackSessionSecurityState(_ state: SessionSecurityState) {
trackSessionSecurityStateCallsCount += 1
trackSessionSecurityStateReceivedState = state
DispatchQueue.main.async {
self.trackSessionSecurityStateReceivedInvocations.append(state)
}
trackSessionSecurityStateClosure?(state)
}
//MARK: - updateUserProperties
var updateUserPropertiesUnderlyingCallsCount = 0
var updateUserPropertiesCallsCount: Int {
get {
if Thread.isMainThread {
return updateUserPropertiesUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = updateUserPropertiesUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
updateUserPropertiesUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
updateUserPropertiesUnderlyingCallsCount = newValue
}
}
}
}
var updateUserPropertiesCalled: Bool {
return updateUserPropertiesCallsCount > 0
}
var updateUserPropertiesReceivedUserProperties: AnalyticsEvent.UserProperties?
var updateUserPropertiesReceivedInvocations: [AnalyticsEvent.UserProperties] = []
var updateUserPropertiesClosure: ((AnalyticsEvent.UserProperties) -> Void)?
func updateUserProperties(_ userProperties: AnalyticsEvent.UserProperties) {
updateUserPropertiesCallsCount += 1
updateUserPropertiesReceivedUserProperties = userProperties
DispatchQueue.main.async {
self.updateUserPropertiesReceivedInvocations.append(userProperties)
}
updateUserPropertiesClosure?(userProperties)
}
//MARK: - trackPinUnpinEvent
var trackPinUnpinEventUnderlyingCallsCount = 0
var trackPinUnpinEventCallsCount: Int {
get {
if Thread.isMainThread {
return trackPinUnpinEventUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = trackPinUnpinEventUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
trackPinUnpinEventUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
trackPinUnpinEventUnderlyingCallsCount = newValue
}
}
}
}
var trackPinUnpinEventCalled: Bool {
return trackPinUnpinEventCallsCount > 0
}
var trackPinUnpinEventReceivedEvent: AnalyticsEvent.PinUnpinAction?
var trackPinUnpinEventReceivedInvocations: [AnalyticsEvent.PinUnpinAction] = []
var trackPinUnpinEventClosure: ((AnalyticsEvent.PinUnpinAction) -> Void)?
func trackPinUnpinEvent(_ event: AnalyticsEvent.PinUnpinAction) {
trackPinUnpinEventCallsCount += 1
trackPinUnpinEventReceivedEvent = event
DispatchQueue.main.async {
self.trackPinUnpinEventReceivedInvocations.append(event)
}
trackPinUnpinEventClosure?(event)
}
}
class AppLockServiceMock: AppLockServiceProtocol, @unchecked Sendable {
var isMandatory: Bool {
get { return underlyingIsMandatory }
@@ -16,7 +16,7 @@ struct LoginScreenCoordinatorParameters {
let loginHint: String?
let userIndicatorController: UserIndicatorControllerProtocol
let appSettings: AppSettings
let analytics: AnalyticsService
let analytics: AnalyticsServiceProtocol
}
enum LoginScreenCoordinatorAction {
@@ -15,7 +15,7 @@ class LoginScreenViewModel: LoginScreenViewModelType, LoginScreenViewModelProtoc
private let authenticationService: AuthenticationServiceProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let appSettings: AppSettings
private let analytics: AnalyticsService
private let analytics: AnalyticsServiceProtocol
private var actionsSubject: PassthroughSubject<LoginScreenViewModelAction, Never> = .init()
var actions: AnyPublisher<LoginScreenViewModelAction, Never> {
@@ -26,7 +26,7 @@ class LoginScreenViewModel: LoginScreenViewModelType, LoginScreenViewModelProtoc
loginHint: String?,
userIndicatorController: UserIndicatorControllerProtocol,
appSettings: AppSettings,
analytics: AnalyticsService) {
analytics: AnalyticsServiceProtocol) {
self.authenticationService = authenticationService
self.userIndicatorController = userIndicatorController
self.appSettings = appSettings
@@ -158,14 +158,11 @@ struct LoginScreen_Previews: PreviewProvider, TestablePreview {
Task { await authenticationService.configure(for: homeserverAddress, flow: .login) }
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
let viewModel = LoginScreenViewModel(authenticationService: authenticationService,
loginHint: nil,
userIndicatorController: UserIndicatorControllerMock(),
appSettings: appSettings,
analytics: analytics)
appSettings: AppSettings(),
analytics: AnalyticsServiceMock.default())
if withCredentials {
viewModel.context.username = "alice"
@@ -16,7 +16,7 @@ struct CallScreenCoordinatorParameters {
let allowPictureInPicture: Bool
let appSettings: AppSettings
let appHooks: AppHooks
let analytics: AnalyticsService
let analytics: AnalyticsServiceProtocol
}
enum CallScreenCoordinatorAction {
@@ -18,7 +18,7 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
private let configuration: ElementCallConfiguration
private let isPictureInPictureAllowed: Bool
private let appSettings: AppSettings
private let analyticsService: AnalyticsService
private let analyticsService: AnalyticsServiceProtocol
private let widgetDriver: ElementCallWidgetDriverProtocol
@@ -41,7 +41,7 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
allowPictureInPicture: Bool,
appHooks: AppHooks,
appSettings: AppSettings,
analyticsService: AnalyticsService) {
analyticsService: AnalyticsServiceProtocol) {
self.elementCallService = elementCallService
self.configuration = configuration
self.appSettings = appSettings
@@ -364,9 +364,6 @@ struct CallScreen_Previews: PreviewProvider {
roomProxy.elementCallWidgetDriverDeviceIDReturnValue = widgetDriver
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
return CallScreenViewModel(elementCallService: ElementCallServiceMock(.init()),
configuration: .init(roomProxy: roomProxy,
clientProxy: clientProxy,
@@ -377,8 +374,8 @@ struct CallScreen_Previews: PreviewProvider {
colorScheme: .light),
allowPictureInPicture: false,
appHooks: AppHooks(),
appSettings: appSettings,
analyticsService: analytics)
appSettings: AppSettings(),
analyticsService: AnalyticsServiceMock.default())
}()
static var previews: some View {
@@ -14,7 +14,7 @@ struct PollFormScreenCoordinatorParameters {
/// The max number of allowed options, if no value provided the default value of the view model will be used.
var maxNumberOfOptions: Int?
let timelineController: TimelineControllerProtocol
let analytics: AnalyticsService
let analytics: AnalyticsServiceProtocol
let userIndicatorController: UserIndicatorControllerProtocol
}
@@ -13,7 +13,7 @@ typealias PollFormScreenViewModelType = StateStoreViewModelV2<PollFormScreenView
class PollFormScreenViewModel: PollFormScreenViewModelType, PollFormScreenViewModelProtocol {
private let timelineController: TimelineControllerProtocol
private let analytics: AnalyticsService
private let analytics: AnalyticsServiceProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private var actionsSubject: PassthroughSubject<PollFormScreenViewModelAction, Never> = .init()
@@ -24,7 +24,7 @@ class PollFormScreenViewModel: PollFormScreenViewModelType, PollFormScreenViewMo
init(mode: PollFormMode,
maxNumberOfOptions: Int? = nil,
timelineController: TimelineControllerProtocol,
analytics: AnalyticsService,
analytics: AnalyticsServiceProtocol,
userIndicatorController: UserIndicatorControllerProtocol) {
self.timelineController = timelineController
self.analytics = analytics
@@ -227,7 +227,7 @@ struct PollFormScreen_Previews: PreviewProvider, TestablePreview {
static func makeViewModel(mode: PollFormMode) -> PollFormScreenViewModel {
PollFormScreenViewModel(mode: mode,
timelineController: MockTimelineController(),
analytics: .mock(),
analytics: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default)
}
}
@@ -16,7 +16,7 @@ struct CreateRoomScreenCoordinatorParameters {
let userSession: UserSessionProtocol
let userIndicatorController: UserIndicatorControllerProtocol
let appSettings: AppSettings
let analytics: AnalyticsService
let analytics: AnalyticsServiceProtocol
}
enum CreateRoomScreenCoordinatorAction {
@@ -15,7 +15,7 @@ typealias CreateRoomScreenViewModelType = StateStoreViewModel<CreateRoomScreenVi
class CreateRoomScreenViewModel: CreateRoomScreenViewModelType, CreateRoomScreenViewModelProtocol {
private let userSession: UserSessionProtocol
private let mediaUploadingPreprocessor: MediaUploadingPreprocessor
private let analytics: AnalyticsService
private let analytics: AnalyticsServiceProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private var syncNameAndAlias = true
@@ -31,7 +31,7 @@ class CreateRoomScreenViewModel: CreateRoomScreenViewModelType, CreateRoomScreen
spaceSelectionMode: CreateRoomScreenSpaceSelectionMode,
shouldShowCancelButton: Bool,
userSession: UserSessionProtocol,
analytics: AnalyticsService,
analytics: AnalyticsServiceProtocol,
userIndicatorController: UserIndicatorControllerProtocol,
appSettings: AppSettings) {
self.userSession = userSession
@@ -461,7 +461,7 @@ struct CreateRoom_Previews: PreviewProvider, TestablePreview {
spaceSelectionMode: selectionMode,
shouldShowCancelButton: isSpace,
userSession: userSession,
analytics: .mock(settings: appSettings),
analytics: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
appSettings: appSettings)
}
@@ -68,13 +68,11 @@ struct CreateRoomSpaceSelectionSheet_Previews: PreviewProvider, TestablePreview
let userSession = UserSessionMock(.init(clientProxy: clientProxy))
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
return CreateRoomScreenViewModel(isSpace: false,
spaceSelectionMode: .editableSpacesList(preSelectedSpace: nil),
shouldShowCancelButton: false,
userSession: userSession,
analytics: analytics,
analytics: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
appSettings: appSettings)
}()
@@ -14,7 +14,7 @@ struct HomeScreenCoordinatorParameters {
let bugReportService: BugReportServiceProtocol
let selectedRoomPublisher: CurrentValuePublisher<String?, Never>
let appSettings: AppSettings
let analyticsService: AnalyticsService
let analyticsService: AnalyticsServiceProtocol
let notificationManager: NotificationManagerProtocol
let userIndicatorController: UserIndicatorControllerProtocol
}
@@ -16,7 +16,7 @@ typealias HomeScreenViewModelType = StateStoreViewModel<HomeScreenViewState, Hom
class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol {
private let userSession: UserSessionProtocol
private let spaceFilterSubject: CurrentValueSubject<SpaceServiceFilter?, Never>
private let analyticsService: AnalyticsService
private let analyticsService: AnalyticsServiceProtocol
private let appSettings: AppSettings
private let notificationManager: NotificationManagerProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
@@ -32,7 +32,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
init(userSession: UserSessionProtocol,
selectedRoomPublisher: CurrentValuePublisher<String?, Never>,
appSettings: AppSettings,
analyticsService: AnalyticsService,
analyticsService: AnalyticsServiceProtocol,
notificationManager: NotificationManagerProtocol,
userIndicatorController: UserIndicatorControllerProtocol) {
self.userSession = userSession
@@ -222,13 +222,10 @@ struct HomeScreen_Previews: PreviewProvider, TestablePreview {
let userSession = UserSessionMock(.init(clientProxy: clientProxy))
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
return HomeScreenViewModel(userSession: userSession,
selectedRoomPublisher: CurrentValueSubject<String?, Never>(nil).asCurrentValuePublisher(),
appSettings: appSettings,
analyticsService: analytics,
appSettings: AppSettings(),
analyticsService: AnalyticsServiceMock.default(),
notificationManager: NotificationManagerMock(),
userIndicatorController: UserIndicatorControllerMock.default)
}
@@ -143,13 +143,10 @@ struct HomeScreenEmptyStateView_Previews: PreviewProvider, TestablePreview {
static let viewModel = {
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "@user:example.com",
roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded([])))))))
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
return HomeScreenViewModel(userSession: userSession,
selectedRoomPublisher: CurrentValueSubject<String?, Never>(nil).asCurrentValuePublisher(),
appSettings: appSettings,
analyticsService: analytics,
appSettings: AppSettings(),
analyticsService: AnalyticsServiceMock.default(),
notificationManager: NotificationManagerMock(),
userIndicatorController: UserIndicatorControllerMock.default)
}()
@@ -200,13 +200,10 @@ struct HomeScreenInviteCell_Previews: PreviewProvider, TestablePreview {
let userSession = UserSessionMock(.init(clientProxy: clientProxy))
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
return HomeScreenViewModel(userSession: userSession,
selectedRoomPublisher: CurrentValueSubject<String?, Never>(nil).asCurrentValuePublisher(),
appSettings: appSettings,
analyticsService: analytics,
appSettings: AppSettings(),
analyticsService: AnalyticsServiceMock.default(),
notificationManager: NotificationManagerMock(),
userIndicatorController: UserIndicatorControllerMock.default)
}
@@ -132,13 +132,10 @@ struct HomeScreenKnockedCell_Previews: PreviewProvider, TestablePreview {
let userSession = UserSessionMock(.init(clientProxy: clientProxy))
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
return HomeScreenViewModel(userSession: userSession,
selectedRoomPublisher: CurrentValueSubject<String?, Never>(nil).asCurrentValuePublisher(),
appSettings: appSettings,
analyticsService: analytics,
appSettings: AppSettings(),
analyticsService: AnalyticsServiceMock.default(),
notificationManager: NotificationManagerMock(),
userIndicatorController: UserIndicatorControllerMock.default)
}
@@ -121,13 +121,10 @@ struct HomeScreenRecoveryKeyConfirmationBanner_Previews: PreviewProvider, Testab
let userSession = UserSessionMock(.init(clientProxy: clientProxy))
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
return HomeScreenViewModel(userSession: userSession,
selectedRoomPublisher: CurrentValueSubject<String?, Never>(nil).asCurrentValuePublisher(),
appSettings: appSettings,
analyticsService: analytics,
appSettings: AppSettings(),
analyticsService: AnalyticsServiceMock.default(),
notificationManager: NotificationManagerMock(),
userIndicatorController: UserIndicatorControllerMock.default)
}
@@ -253,13 +253,10 @@ struct HomeScreenRoomCell_Previews: PreviewProvider, TestablePreview {
static func makeViewModel(roomSummaryProvider: RoomSummaryProviderProtocol) -> HomeScreenViewModel {
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "John Doe", roomSummaryProvider: roomSummaryProvider))))
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
return HomeScreenViewModel(userSession: userSession,
selectedRoomPublisher: CurrentValueSubject<String?, Never>(nil).asCurrentValuePublisher(),
appSettings: appSettings,
analyticsService: analytics,
appSettings: AppSettings(),
analyticsService: AnalyticsServiceMock.default(),
notificationManager: NotificationManagerMock(),
userIndicatorController: UserIndicatorControllerMock.default)
}
@@ -16,7 +16,7 @@ struct LocationSharingScreenCoordinatorParameters {
let timelineController: TimelineControllerProtocol
let liveLocationManager: LiveLocationManagerProtocol
let appMediator: AppMediatorProtocol
let analytics: AnalyticsService
let analytics: AnalyticsServiceProtocol
let userIndicatorController: UserIndicatorControllerProtocol
let mediaProvider: MediaProviderProtocol
}
@@ -17,7 +17,7 @@ class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, Locati
private let roomProxy: JoinedRoomProxyProtocol
private let timelineController: TimelineControllerProtocol
private let liveLocationManager: LiveLocationManagerProtocol
private let analytics: AnalyticsService
private let analytics: AnalyticsServiceProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let notificationCenter: NotificationCenter
@@ -36,7 +36,7 @@ class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, Locati
roomProxy: JoinedRoomProxyProtocol,
timelineController: TimelineControllerProtocol,
liveLocationManager: LiveLocationManagerProtocol,
analytics: AnalyticsService,
analytics: AnalyticsServiceProtocol,
userIndicatorController: UserIndicatorControllerProtocol,
mediaProvider: MediaProviderProtocol,
notificationCenter: NotificationCenter = .default) {
@@ -389,15 +389,12 @@ extension LocationSharingScreenViewModel {
let roomProxy = JoinedRoomProxyMock(.init(members: .allMembers, ownUserID: RoomMemberProxyMock.mockMe.userID))
roomProxy.makeLiveLocationServiceReturnValue = liveLocationServiceMock
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
return LocationSharingScreenViewModel(interactionMode: interactionMode,
mapURLBuilder: appSettings.mapTilerConfiguration,
mapURLBuilder: AppSettings().mapTilerConfiguration,
roomProxy: roomProxy,
timelineController: MockTimelineController(),
liveLocationManager: LiveLocationManagerMock(),
analytics: analytics,
analytics: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock(),
mediaProvider: MediaProviderMock(configuration: .init()))
}
@@ -15,7 +15,7 @@ typealias ManageRoomMemberSheetViewModelType = StateStoreViewModelV2<ManageRoomM
class ManageRoomMemberSheetViewModel: ManageRoomMemberSheetViewModelType, ManageRoomMemberSheetViewModelProtocol {
private let roomProxy: JoinedRoomProxyProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let analyticsService: AnalyticsService
private let analyticsService: AnalyticsServiceProtocol
private var actionsSubject: PassthroughSubject<ManageRoomMemberSheetViewModelAction, Never> = .init()
@@ -27,7 +27,7 @@ class ManageRoomMemberSheetViewModel: ManageRoomMemberSheetViewModelType, Manage
permissions: ManageRoomMemberPermissions,
roomProxy: JoinedRoomProxyProtocol,
userIndicatorController: UserIndicatorControllerProtocol,
analyticsService: AnalyticsService,
analyticsService: AnalyticsServiceProtocol,
mediaProvider: MediaProviderProtocol) {
self.userIndicatorController = userIndicatorController
self.roomProxy = roomProxy
@@ -120,7 +120,7 @@ private extension ManageRoomMemberSheetViewModel {
ownPowerLevel: powerLevel),
roomProxy: JoinedRoomProxyMock(.init()),
userIndicatorController: UserIndicatorControllerMock(),
analyticsService: .mock(),
analyticsService: AnalyticsServiceMock.default(),
mediaProvider: MediaProviderMock(configuration: .init()))
}
}
@@ -17,7 +17,7 @@ struct MediaEventsTimelineScreenCoordinatorParameters {
let mediaPlayerProvider: MediaPlayerProviderProtocol
let appMediator: AppMediatorProtocol
let appSettings: AppSettings
let analytics: AnalyticsService
let analytics: AnalyticsServiceProtocol
let emojiProvider: EmojiProviderProtocol
let linkMetadataProvider: LinkMetadataProviderProtocol
let userIndicatorController: UserIndicatorControllerProtocol
@@ -311,8 +311,6 @@ struct MediaEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
}
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
return TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Preview room")),
timelineController: timelineController,
userSession: UserSessionMock(.init()),
@@ -320,7 +318,7 @@ struct MediaEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
userIndicatorController: UserIndicatorControllerMock(),
appMediator: AppMediatorMock.default,
appSettings: appSettings,
analyticsService: analytics,
analyticsService: AnalyticsServiceMock.default(),
emojiProvider: EmojiProvider(appSettings: appSettings),
linkMetadataProvider: LinkMetadataProvider(),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
@@ -14,7 +14,7 @@ enum AnalyticsPromptScreenCoordinatorAction {
}
final class AnalyticsPromptScreenCoordinator: CoordinatorProtocol {
private let analytics: AnalyticsService
private let analytics: AnalyticsServiceProtocol
private var viewModel: AnalyticsPromptScreenViewModelProtocol
private let actionsSubject: PassthroughSubject<AnalyticsPromptScreenCoordinatorAction, Never> = .init()
private var cancellables = Set<AnyCancellable>()
@@ -23,7 +23,7 @@ final class AnalyticsPromptScreenCoordinator: CoordinatorProtocol {
actionsSubject.eraseToAnyPublisher()
}
init(analytics: AnalyticsService, termsURL: URL?) {
init(analytics: AnalyticsServiceProtocol, termsURL: URL?) {
self.analytics = analytics
viewModel = AnalyticsPromptScreenViewModel(termsURL: termsURL)
}
@@ -16,7 +16,7 @@ struct PinnedEventsTimelineScreenCoordinatorParameters {
let mediaPlayerProvider: MediaPlayerProviderProtocol
let appMediator: AppMediatorProtocol
let appSettings: AppSettings
let analytics: AnalyticsService
let analytics: AnalyticsServiceProtocol
let emojiProvider: EmojiProviderProtocol
let linkMetadataProvider: LinkMetadataProviderProtocol
let timelineControllerFactory: TimelineControllerFactoryProtocol
@@ -15,7 +15,7 @@ class PinnedEventsTimelineScreenViewModel: PinnedEventsTimelineScreenViewModelTy
private let roomProxy: JoinedRoomProxyProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let appSettings: AppSettings
private let analyticsService: AnalyticsService
private let analyticsService: AnalyticsServiceProtocol
private let actionsSubject: PassthroughSubject<PinnedEventsTimelineScreenViewModelAction, Never> = .init()
var actionsPublisher: AnyPublisher<PinnedEventsTimelineScreenViewModelAction, Never> {
@@ -25,7 +25,7 @@ class PinnedEventsTimelineScreenViewModel: PinnedEventsTimelineScreenViewModelTy
init(roomProxy: JoinedRoomProxyProtocol,
userIndicatorController: UserIndicatorControllerProtocol,
appSettings: AppSettings,
analyticsService: AnalyticsService) {
analyticsService: AnalyticsServiceProtocol) {
self.roomProxy = roomProxy
self.userIndicatorController = userIndicatorController
self.appSettings = appSettings
@@ -69,15 +69,13 @@ struct PinnedEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = PinnedEventsTimelineScreenViewModel(roomProxy: JoinedRoomProxyMock(.init()),
userIndicatorController: UserIndicatorControllerMock(),
appSettings: AppSettings(),
analyticsService: .mock())
analyticsService: AnalyticsServiceMock.default())
static let emptyTimelineViewModel: TimelineViewModel = {
let timelineController = MockTimelineController(timelineKind: .pinned)
timelineController.timelineItems = []
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
return TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Preview room")),
timelineController: timelineController,
userSession: UserSessionMock(.init()),
@@ -85,7 +83,7 @@ struct PinnedEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
userIndicatorController: UserIndicatorControllerMock(),
appMediator: AppMediatorMock.default,
appSettings: appSettings,
analyticsService: analytics,
analyticsService: AnalyticsServiceMock.default(),
emojiProvider: EmojiProvider(appSettings: appSettings),
linkMetadataProvider: LinkMetadataProvider(),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
@@ -14,7 +14,7 @@ struct RoomChangePermissionsScreenCoordinatorParameters {
let permissions: RoomPermissions
let roomProxy: JoinedRoomProxyProtocol
let userIndicatorController: UserIndicatorControllerProtocol
let analytics: AnalyticsService
let analytics: AnalyticsServiceProtocol
}
enum RoomChangePermissionsScreenCoordinatorAction {
@@ -15,7 +15,7 @@ typealias RoomChangePermissionsScreenViewModelType = StateStoreViewModelV2<RoomC
class RoomChangePermissionsScreenViewModel: RoomChangePermissionsScreenViewModelType, RoomChangePermissionsScreenViewModelProtocol {
private let roomProxy: JoinedRoomProxyProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let analytics: AnalyticsService
private let analytics: AnalyticsServiceProtocol
private var actionsSubject: PassthroughSubject<RoomChangePermissionsScreenViewModelAction, Never> = .init()
var actionsPublisher: AnyPublisher<RoomChangePermissionsScreenViewModelAction, Never> {
@@ -26,7 +26,7 @@ class RoomChangePermissionsScreenViewModel: RoomChangePermissionsScreenViewModel
ownPowerLevel: RoomPowerLevel,
roomProxy: JoinedRoomProxyProtocol,
userIndicatorController: UserIndicatorControllerProtocol,
analytics: AnalyticsService) {
analytics: AnalyticsServiceProtocol) {
self.roomProxy = roomProxy
self.userIndicatorController = userIndicatorController
self.analytics = analytics
@@ -91,6 +91,6 @@ struct RoomChangePermissionsScreen_Previews: PreviewProvider, TestablePreview {
ownPowerLevel: ownPowerLevel,
roomProxy: JoinedRoomProxyMock(.init(isSpace: isSpace)),
userIndicatorController: UserIndicatorControllerMock(),
analytics: .mock())
analytics: AnalyticsServiceMock.default())
}
}
@@ -14,7 +14,7 @@ struct RoomChangeRolesScreenCoordinatorParameters {
let roomProxy: JoinedRoomProxyProtocol
let mediaProvider: MediaProviderProtocol
let userIndicatorController: UserIndicatorControllerProtocol
let analytics: AnalyticsService
let analytics: AnalyticsServiceProtocol
}
enum RoomChangeRolesScreenCoordinatorAction {
@@ -14,7 +14,7 @@ typealias RoomChangeRolesScreenViewModelType = StateStoreViewModelV2<RoomChangeR
class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomChangeRolesScreenViewModelProtocol {
private let roomProxy: JoinedRoomProxyProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let analytics: AnalyticsService
private let analytics: AnalyticsServiceProtocol
private let actionsSubject: PassthroughSubject<RoomChangeRolesScreenViewModelAction, Never> = .init()
var actionsPublisher: AnyPublisher<RoomChangeRolesScreenViewModelAction, Never> {
@@ -25,7 +25,7 @@ class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomCh
roomProxy: JoinedRoomProxyProtocol,
mediaProvider: MediaProviderProtocol,
userIndicatorController: UserIndicatorControllerProtocol,
analytics: AnalyticsService) {
analytics: AnalyticsServiceProtocol) {
guard mode != .user || mode != .creator else { fatalError("Invalid screen configuration: \(mode)") }
self.roomProxy = roomProxy
@@ -151,6 +151,6 @@ struct RoomChangeRolesScreen_Previews: PreviewProvider, TestablePreview {
roomProxy: JoinedRoomProxyMock(.init(members: members)),
mediaProvider: MediaProviderMock(configuration: .init()),
userIndicatorController: UserIndicatorControllerMock(),
analytics: .mock())
analytics: AnalyticsServiceMock.default())
}
}
@@ -12,7 +12,7 @@ import SwiftUI
struct RoomDetailsScreenCoordinatorParameters {
let roomProxy: JoinedRoomProxyProtocol
let userSession: UserSessionProtocol
let analyticsService: AnalyticsService
let analyticsService: AnalyticsServiceProtocol
let userIndicatorController: UserIndicatorControllerProtocol
let notificationSettings: NotificationSettingsProxyProtocol
let attributedStringBuilder: AttributedStringBuilderProtocol
@@ -14,7 +14,7 @@ typealias RoomDetailsScreenViewModelType = StateStoreViewModelV2<RoomDetailsScre
class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScreenViewModelProtocol {
private let roomProxy: JoinedRoomProxyProtocol
private let userSession: UserSessionProtocol
private let analyticsService: AnalyticsService
private let analyticsService: AnalyticsServiceProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let notificationSettingsProxy: NotificationSettingsProxyProtocol
private let attributedStringBuilder: AttributedStringBuilderProtocol
@@ -46,7 +46,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
init(roomProxy: JoinedRoomProxyProtocol,
userSession: UserSessionProtocol,
analyticsService: AnalyticsService,
analyticsService: AnalyticsServiceProtocol,
userIndicatorController: UserIndicatorControllerProtocol,
notificationSettingsProxy: NotificationSettingsProxyProtocol,
attributedStringBuilder: AttributedStringBuilderProtocol,
@@ -418,16 +418,13 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
let notificationSettingsProxy = NotificationSettingsProxyMock(with: notificationSettingsProxyMockConfiguration)
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
return .init(roomProxy: roomProxy,
userSession: UserSessionMock(.init()),
analyticsService: analytics,
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: notificationSettingsProxy,
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
}
private static func makeSimpleRoomViewModel() -> RoomDetailsScreenViewModel {
@@ -449,16 +446,13 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
let notificationSettingsProxy = NotificationSettingsProxyMock(with: .init())
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
return .init(roomProxy: roomProxy,
userSession: UserSessionMock(.init()),
analyticsService: analytics,
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: notificationSettingsProxy,
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
}
private static func makeDMViewModel(verificationState: UserIdentityVerificationState) -> RoomDetailsScreenViewModel {
@@ -490,15 +484,12 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
let notificationSettingsProxy = NotificationSettingsProxyMock(with: .init())
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
return .init(roomProxy: roomProxy,
userSession: UserSessionMock(.init(clientProxy: clientProxyMock)),
analyticsService: analytics,
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: notificationSettingsProxy,
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
appSettings: AppSettings())
}
}
@@ -14,7 +14,7 @@ struct RoomMemberDetailsScreenCoordinatorParameters {
let roomProxy: JoinedRoomProxyProtocol
let userSession: UserSessionProtocol
let userIndicatorController: UserIndicatorControllerProtocol
let analytics: AnalyticsService
let analytics: AnalyticsServiceProtocol
let appSettings: AppSettings
}
@@ -15,7 +15,7 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro
private let roomProxy: JoinedRoomProxyProtocol
private let userSession: UserSessionProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let analytics: AnalyticsService
private let analytics: AnalyticsServiceProtocol
private let appSettings: AppSettings
private var actionsSubject: PassthroughSubject<RoomMemberDetailsScreenViewModelAction, Never> = .init()
@@ -30,7 +30,7 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro
roomProxy: JoinedRoomProxyProtocol,
userSession: UserSessionProtocol,
userIndicatorController: UserIndicatorControllerProtocol,
analytics: AnalyticsService,
analytics: AnalyticsServiceProtocol,
appSettings: AppSettings) {
self.roomProxy = roomProxy
self.userSession = userSession
@@ -236,14 +236,11 @@ struct RoomMemberDetailsScreen_Previews: PreviewProvider, TestablePreview {
clientProxyMock.directRoomForUserIDReturnValue = .success("roomID")
}
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
return RoomMemberDetailsScreenViewModel(userID: member.userID,
roomProxy: roomProxyMock,
userSession: UserSessionMock(.init(clientProxy: clientProxyMock)),
userIndicatorController: UserIndicatorControllerMock.default,
analytics: analytics,
appSettings: appSettings)
analytics: AnalyticsServiceMock.default(),
appSettings: AppSettings())
}
}
@@ -13,7 +13,7 @@ struct RoomMembersListScreenCoordinatorParameters {
let userSession: UserSessionProtocol
let roomProxy: JoinedRoomProxyProtocol
let userIndicatorController: UserIndicatorControllerProtocol
let analytics: AnalyticsService
let analytics: AnalyticsServiceProtocol
}
enum RoomMembersListScreenCoordinatorAction {
@@ -15,7 +15,7 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
private let userSession: UserSessionProtocol
private let roomProxy: JoinedRoomProxyProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let analytics: AnalyticsService
private let analytics: AnalyticsServiceProtocol
private var members: [RoomMemberProxyProtocol] = []
private var currentUserProxy: RoomMemberProxyProtocol?
@@ -30,7 +30,7 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
userSession: UserSessionProtocol,
roomProxy: JoinedRoomProxyProtocol,
userIndicatorController: UserIndicatorControllerProtocol,
analytics: AnalyticsService) {
analytics: AnalyticsServiceProtocol) {
self.userSession = userSession
self.roomProxy = roomProxy
self.userIndicatorController = userIndicatorController
@@ -262,6 +262,6 @@ struct RoomMembersListScreen_Previews: PreviewProvider, TestablePreview {
ownUserID: ownUserID,
powerLevelsConfiguration: .init(canUserInvite: false))),
userIndicatorController: UserIndicatorControllerMock.default,
analytics: .mock())
analytics: AnalyticsServiceMock.default())
}
}
@@ -132,7 +132,7 @@ struct RoomMembersListMemberCell_Previews: PreviewProvider, TestablePreview {
static let viewModel = RoomMembersListScreenViewModel(userSession: UserSessionMock(.init()),
roomProxy: JoinedRoomProxyMock(.init(name: "Some room", members: [])),
userIndicatorController: UserIndicatorControllerMock.default,
analytics: .mock())
analytics: AnalyticsServiceMock.default())
static var previews: some View {
VStack(spacing: 0) {
Section("Invited/Joined") {
@@ -12,7 +12,7 @@ import SwiftUI
struct RoomRolesAndPermissionsScreenCoordinatorParameters {
let roomProxy: JoinedRoomProxyProtocol
let userIndicatorController: UserIndicatorControllerProtocol
let analytics: AnalyticsService
let analytics: AnalyticsServiceProtocol
}
enum RoomRolesAndPermissionsScreenCoordinatorAction {
@@ -14,7 +14,7 @@ typealias RoomRolesAndPermissionsScreenViewModelType = StateStoreViewModelV2<Roo
class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewModelType, RoomRolesAndPermissionsScreenViewModelProtocol {
private let roomProxy: JoinedRoomProxyProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let analytics: AnalyticsService
private let analytics: AnalyticsServiceProtocol
private var ownUser: RoomMemberDetails?
private var actionsSubject: PassthroughSubject<RoomRolesAndPermissionsScreenViewModelAction, Never> = .init()
@@ -22,7 +22,7 @@ class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewM
actionsSubject.eraseToAnyPublisher()
}
init(initialPermissions: RoomPermissions? = nil, roomProxy: JoinedRoomProxyProtocol, userIndicatorController: UserIndicatorControllerProtocol, analytics: AnalyticsService) {
init(initialPermissions: RoomPermissions? = nil, roomProxy: JoinedRoomProxyProtocol, userIndicatorController: UserIndicatorControllerProtocol, analytics: AnalyticsServiceProtocol) {
self.roomProxy = roomProxy
self.userIndicatorController = userIndicatorController
self.analytics = analytics
@@ -121,12 +121,12 @@ struct RoomRolesAndPermissionsScreen_Previews: PreviewProvider, TestablePreview
static let viewModel = RoomRolesAndPermissionsScreenViewModel(initialPermissions: RoomPermissions(powerLevels: .mock),
roomProxy: JoinedRoomProxyMock(.init(members: .allMembersAsAdmin)),
userIndicatorController: UserIndicatorControllerMock(),
analytics: .mock())
analytics: AnalyticsServiceMock.default())
static let creatorViewModel = RoomRolesAndPermissionsScreenViewModel(initialPermissions: RoomPermissions(powerLevels: .mock),
roomProxy: JoinedRoomProxyMock(.init(members: .allMembersAsCreator)),
userIndicatorController: UserIndicatorControllerMock(),
analytics: .mock())
analytics: AnalyticsServiceMock.default())
static var previews: some View {
ElementNavigationStack {
RoomRolesAndPermissionsScreen(context: viewModel.context)
@@ -20,7 +20,7 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
private let wysiwygViewModel: WysiwygComposerViewModel
private let completionSuggestionService: CompletionSuggestionServiceProtocol
private let roomProxy: JoinedRoomProxyProtocol
private let analyticsService: AnalyticsService
private let analyticsService: AnalyticsServiceProtocol
private let draftService: ComposerDraftServiceProtocol
private var identityPinningViolations = [String: RoomMemberProxyProtocol]()
@@ -52,7 +52,7 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
mediaProvider: MediaProviderProtocol,
mentionDisplayHelper: MentionDisplayHelper,
appSettings: AppSettings,
analyticsService: AnalyticsService,
analyticsService: AnalyticsServiceProtocol,
composerDraftService: ComposerDraftServiceProtocol) {
self.initialText = initialText
self.wysiwygViewModel = wysiwygViewModel
@@ -820,17 +820,14 @@ extension ComposerToolbarViewModel {
roomProxy.identityStatusChangesPublisher = .init([.init(userId: RoomMemberProxyMock.mockAlice.userID, changedTo: .verificationViolation)])
}
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
let wysiwygViewModel = WysiwygComposerViewModel()
let viewModel = ComposerToolbarViewModel(roomProxy: roomProxy,
wysiwygViewModel: wysiwygViewModel,
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init(suggestions: suggestions)),
mediaProvider: MediaProviderMock(configuration: .init()),
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
appSettings: appSettings,
analyticsService: analytics,
appSettings: AppSettings(),
analyticsService: AnalyticsServiceMock.default(),
composerDraftService: ComposerDraftServiceMock(.init()))
viewModel.state.bindings.composerFocused = focused
viewModel.state.bindings.plainComposerText = NSAttributedString(string: message)
@@ -89,7 +89,7 @@ struct RoomAttachmentPicker_Previews: PreviewProvider, TestablePreview {
mediaProvider: MediaProviderMock(configuration: .init()),
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
appSettings: appSettings,
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
composerDraftService: ComposerDraftServiceMock(.init()))
}
@@ -26,7 +26,7 @@ struct RoomScreenCoordinatorParameters {
let appMediator: AppMediatorProtocol
let appSettings: AppSettings
let appHooks: AppHooks
let analytics: AnalyticsService
let analytics: AnalyticsServiceProtocol
let composerDraftService: ComposerDraftServiceProtocol
let timelineControllerFactory: TimelineControllerFactoryProtocol
let userIndicatorController: UserIndicatorControllerProtocol
@@ -18,7 +18,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
private let clientProxy: ClientProxyProtocol
private let roomProxy: JoinedRoomProxyProtocol
private let appSettings: AppSettings
private let analyticsService: AnalyticsService
private let analyticsService: AnalyticsServiceProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private var initialSelectedPinnedEventID: String?
@@ -56,7 +56,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
ongoingCallRoomIDPublisher: CurrentValuePublisher<String?, Never>,
appSettings: AppSettings,
appHooks: AppHooks,
analyticsService: AnalyticsService,
analyticsService: AnalyticsServiceProtocol,
userIndicatorController: UserIndicatorControllerProtocol) {
clientProxy = userSession.clientProxy
self.roomProxy = roomProxy
@@ -457,17 +457,14 @@ extension RoomScreenViewModel {
static func mock(roomProxyMock: JoinedRoomProxyMock,
clientProxyMock: ClientProxyMock = ClientProxyMock(.init()),
appHooks: AppHooks = AppHooks()) -> RoomScreenViewModel {
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
return RoomScreenViewModel(userSession: UserSessionMock(.init(clientProxy: clientProxyMock)),
roomProxy: roomProxyMock,
initialSelectedPinnedEventID: nil,
ongoingCallRoomIDPublisher: .init(.init(nil)),
appSettings: appSettings,
appHooks: appHooks,
analyticsService: analytics,
userIndicatorController: UserIndicatorControllerMock.default)
RoomScreenViewModel(userSession: UserSessionMock(.init(clientProxy: clientProxyMock)),
roomProxy: roomProxyMock,
initialSelectedPinnedEventID: nil,
ongoingCallRoomIDPublisher: .init(.init(nil)),
appSettings: AppSettings(),
appHooks: appHooks,
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default)
}
}
@@ -258,8 +258,6 @@ struct RoomScreen_Previews: PreviewProvider, TestablePreview {
let roomViewModel = RoomScreenViewModel.mock(roomProxyMock: roomProxyMock)
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
let timelineViewModel = TimelineViewModel(roomProxy: roomProxyMock,
timelineController: MockTimelineController(),
userSession: UserSessionMock(.init()),
@@ -267,7 +265,7 @@ struct RoomScreen_Previews: PreviewProvider, TestablePreview {
userIndicatorController: UserIndicatorControllerMock.default,
appMediator: AppMediatorMock.default,
appSettings: appSettings,
analyticsService: analytics,
analyticsService: AnalyticsServiceMock.default(),
emojiProvider: EmojiProvider(appSettings: appSettings),
linkMetadataProvider: LinkMetadataProvider(),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
@@ -11,7 +11,7 @@ import SwiftUI
struct AdvancedSettingsScreenCoordinatorParameters {
let appSettings: AppSettings
let analytics: AnalyticsService
let analytics: AnalyticsServiceProtocol
let clientProxy: ClientProxyProtocol
let userIndicatorController: UserIndicatorControllerProtocol
}
@@ -12,7 +12,7 @@ import SwiftUI
typealias AdvancedSettingsScreenViewModelType = StateStoreViewModelV2<AdvancedSettingsScreenViewState, AdvancedSettingsScreenViewAction>
class AdvancedSettingsScreenViewModel: AdvancedSettingsScreenViewModelType, AdvancedSettingsScreenViewModelProtocol {
private let analytics: AnalyticsService
private let analytics: AnalyticsServiceProtocol
private let clientProxy: ClientProxyProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
@@ -20,7 +20,7 @@ class AdvancedSettingsScreenViewModel: AdvancedSettingsScreenViewModelType, Adva
private var hideInviteAvatarsTask: Task<Void, Never>?
init(advancedSettings: AdvancedSettingsProtocol,
analytics: AnalyticsService,
analytics: AnalyticsServiceProtocol,
clientProxy: ClientProxyProtocol,
userIndicatorController: UserIndicatorControllerProtocol) {
self.analytics = analytics
@@ -164,7 +164,7 @@ struct AdvancedSettingsScreen_Previews: PreviewProvider, TestablePreview {
AppSettings.resetAllSettings()
let appSettings = AppSettings()
return AdvancedSettingsScreenViewModel(advancedSettings: appSettings,
analytics: .mock(settings: appSettings),
analytics: AnalyticsServiceMock.default(),
clientProxy: ClientProxyMock(.init()),
userIndicatorController: UserIndicatorControllerMock.default)
}()
@@ -11,7 +11,7 @@ import SwiftUI
struct AnalyticsSettingsScreenCoordinatorParameters {
let appSettings: AppSettings
let analytics: AnalyticsService
let analytics: AnalyticsServiceProtocol
}
final class AnalyticsSettingsScreenCoordinator: CoordinatorProtocol {
@@ -12,9 +12,9 @@ import SwiftUI
typealias AnalyticsSettingsScreenViewModelType = StateStoreViewModelV2<AnalyticsSettingsScreenViewState, AnalyticsSettingsScreenViewAction>
class AnalyticsSettingsScreenViewModel: AnalyticsSettingsScreenViewModelType, AnalyticsSettingsScreenViewModelProtocol {
private let analytics: AnalyticsService
private let analytics: AnalyticsServiceProtocol
init(appSettings: AppSettings, analytics: AnalyticsService) {
init(appSettings: AppSettings, analytics: AnalyticsServiceProtocol) {
self.analytics = analytics
let strings = AnalyticsSettingsScreenStrings(termsURL: appSettings.analyticsTermsURL)
@@ -40,9 +40,8 @@ struct AnalyticsSettingsScreen: View {
struct AnalyticsSettingsScreen_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
let viewModel = AnalyticsSettingsScreenViewModel(appSettings: appSettings,
analytics: analytics)
analytics: AnalyticsServiceMock.default())
AnalyticsSettingsScreen(context: viewModel.context)
}
}
@@ -126,20 +126,17 @@ struct SpaceSettingsScreen_Previews: PreviewProvider, TestablePreview {
static let userViewModel = makeViewModel(members: .allMembers)
static func makeViewModel(members: [RoomMemberProxyMock]) -> RoomDetailsScreenViewModel {
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
return RoomDetailsScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Space",
avatarURL: .mockMXCAvatar,
isSpace: true,
canonicalAlias: "#space:matrix.org",
members: members)),
userSession: UserSessionMock(.init()),
analyticsService: analytics,
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: appSettings)
RoomDetailsScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Space",
avatarURL: .mockMXCAvatar,
isSpace: true,
canonicalAlias: "#space:matrix.org",
members: members)),
userSession: UserSessionMock(.init()),
analyticsService: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock.default,
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: AppSettings())
}
static var previews: some View {
@@ -14,7 +14,7 @@ struct StartChatScreenCoordinatorParameters {
let userDiscoveryService: UserDiscoveryServiceProtocol
let userIndicatorController: UserIndicatorControllerProtocol
let appSettings: AppSettings
let analytics: AnalyticsService
let analytics: AnalyticsServiceProtocol
}
enum StartChatScreenCoordinatorAction {
@@ -14,7 +14,7 @@ typealias StartChatScreenViewModelType = StateStoreViewModel<StartChatScreenView
class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenViewModelProtocol {
private let userSession: UserSessionProtocol
private let analytics: AnalyticsService
private let analytics: AnalyticsServiceProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let userDiscoveryService: UserDiscoveryServiceProtocol
private let appSettings: AppSettings
@@ -27,7 +27,7 @@ class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenVie
}
init(userSession: UserSessionProtocol,
analytics: AnalyticsService,
analytics: AnalyticsServiceProtocol,
userIndicatorController: UserIndicatorControllerProtocol,
userDiscoveryService: UserDiscoveryServiceProtocol,
appSettings: AppSettings) {
@@ -83,14 +83,11 @@ struct JoinRoomByAddressView_Previews: PreviewProvider, TestablePreview {
let userDiscoveryService = UserDiscoveryServiceMock()
userDiscoveryService.searchProfilesWithReturnValue = .success([.mockAlice])
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
return StartChatScreenViewModel(userSession: userSession,
analytics: analytics,
analytics: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock(),
userDiscoveryService: userDiscoveryService,
appSettings: appSettings)
appSettings: AppSettings())
}()
static var previews: some View {
@@ -159,7 +159,7 @@ struct StartChatScreen_Previews: PreviewProvider, TestablePreview {
let userDiscoveryService = UserDiscoveryServiceMock()
userDiscoveryService.searchProfilesWithReturnValue = .success([.mockAlice])
return StartChatScreenViewModel(userSession: userSession,
analytics: .mock(settings: appSettings),
analytics: AnalyticsServiceMock.default(),
userIndicatorController: UserIndicatorControllerMock(),
userDiscoveryService: userDiscoveryService,
appSettings: appSettings)
@@ -22,7 +22,7 @@ struct ThreadTimelineScreenCoordinatorParameters {
let completionSuggestionService: CompletionSuggestionServiceProtocol
let appMediator: AppMediatorProtocol
let appSettings: AppSettings
let analytics: AnalyticsService
let analytics: AnalyticsServiceProtocol
let composerDraftService: ComposerDraftServiceProtocol
let timelineControllerFactory: TimelineControllerFactoryProtocol
let userIndicatorController: UserIndicatorControllerProtocol
@@ -41,7 +41,7 @@ class TimelineInteractionHandler {
private let userIndicatorController: UserIndicatorControllerProtocol
private let appMediator: AppMediatorProtocol
private let appSettings: AppSettings
private let analyticsService: AnalyticsService
private let analyticsService: AnalyticsServiceProtocol
private let emojiProvider: EmojiProviderProtocol
private let linkMetadataProvider: LinkMetadataProviderProtocol
private let timelineControllerFactory: TimelineControllerFactoryProtocol
@@ -68,7 +68,7 @@ class TimelineInteractionHandler {
userIndicatorController: UserIndicatorControllerProtocol,
appMediator: AppMediatorProtocol,
appSettings: AppSettings,
analyticsService: AnalyticsService,
analyticsService: AnalyticsServiceProtocol,
emojiProvider: EmojiProviderProtocol,
linkMetadataProvider: LinkMetadataProviderProtocol,
timelineControllerFactory: TimelineControllerFactoryProtocol) {
@@ -29,7 +29,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
private let userIndicatorController: UserIndicatorControllerProtocol
private let appMediator: AppMediatorProtocol
private let appSettings: AppSettings
private let analyticsService: AnalyticsService
private let analyticsService: AnalyticsServiceProtocol
private let emojiProvider: EmojiProviderProtocol
private let timelineControllerFactory: TimelineControllerFactoryProtocol
@@ -55,7 +55,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
userIndicatorController: UserIndicatorControllerProtocol,
appMediator: AppMediatorProtocol,
appSettings: AppSettings,
analyticsService: AnalyticsService,
analyticsService: AnalyticsServiceProtocol,
emojiProvider: EmojiProviderProtocol,
linkMetadataProvider: LinkMetadataProviderProtocol,
timelineControllerFactory: TimelineControllerFactoryProtocol) {
@@ -1059,8 +1059,6 @@ extension TimelineViewModel {
let roomProxy = JoinedRoomProxyMock(.init(name: "Preview room", predecessor: hasPredecessor ? .init(roomId: UUID().uuidString) : nil))
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
return TimelineViewModel(roomProxy: roomProxy,
focussedEventID: nil,
timelineController: timelineController ?? MockTimelineController(timelineKind: timelineKind),
@@ -1069,7 +1067,7 @@ extension TimelineViewModel {
userIndicatorController: UserIndicatorControllerMock.default,
appMediator: AppMediatorMock.default,
appSettings: appSettings,
analyticsService: analytics,
analyticsService: AnalyticsServiceMock.default(),
emojiProvider: EmojiProvider(appSettings: appSettings),
linkMetadataProvider: LinkMetadataProvider(),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
@@ -46,7 +46,6 @@ struct ReadReceiptsSummaryView_Previews: PreviewProvider, TestablePreview {
let roomProxyMock = JoinedRoomProxyMock(.init(name: "Room", members: members))
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
return TimelineViewModel(roomProxy: roomProxyMock,
timelineController: MockTimelineController(),
@@ -55,7 +54,7 @@ struct ReadReceiptsSummaryView_Previews: PreviewProvider, TestablePreview {
userIndicatorController: UserIndicatorControllerMock(),
appMediator: AppMediatorMock.default,
appSettings: appSettings,
analyticsService: analytics,
analyticsService: AnalyticsServiceMock.default(),
emojiProvider: EmojiProvider(appSettings: appSettings),
linkMetadataProvider: LinkMetadataProvider(),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
@@ -346,7 +346,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
userIndicatorController: UserIndicatorControllerMock.default,
appMediator: AppMediatorMock.default,
appSettings: appSettings,
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
emojiProvider: EmojiProvider(appSettings: appSettings),
linkMetadataProvider: LinkMetadataProvider(),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
@@ -365,7 +365,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
userIndicatorController: UserIndicatorControllerMock.default,
appMediator: AppMediatorMock.default,
appSettings: appSettings,
analyticsService: .mock(settings: appSettings),
analyticsService: AnalyticsServiceMock.default(),
emojiProvider: EmojiProvider(appSettings: appSettings),
linkMetadataProvider: LinkMetadataProvider(),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
@@ -81,8 +81,6 @@ struct TimelineReadReceiptsView_Previews: PreviewProvider, TestablePreview {
static let viewModel = {
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
return TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Test", members: members)),
timelineController: MockTimelineController(),
userSession: UserSessionMock(.init()),
@@ -90,7 +88,7 @@ struct TimelineReadReceiptsView_Previews: PreviewProvider, TestablePreview {
userIndicatorController: UserIndicatorControllerMock.default,
appMediator: AppMediatorMock.default,
appSettings: appSettings,
analyticsService: analytics,
analyticsService: AnalyticsServiceMock.default(),
emojiProvider: EmojiProvider(appSettings: appSettings),
linkMetadataProvider: LinkMetadataProvider(),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
@@ -91,8 +91,6 @@ struct HighlightedTimelineItemTimeline_Previews: PreviewProvider {
static let composerViewModel = ComposerToolbarViewModel.mock()
static let timelineViewModel = {
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
return TimelineViewModel(roomProxy: roomProxyMock,
focussedEventID: focussedEventID,
timelineController: MockTimelineController(),
@@ -101,7 +99,7 @@ struct HighlightedTimelineItemTimeline_Previews: PreviewProvider {
userIndicatorController: UserIndicatorControllerMock.default,
appMediator: AppMediatorMock.default,
appSettings: appSettings,
analyticsService: analytics,
analyticsService: AnalyticsServiceMock.default(),
emojiProvider: EmojiProvider(appSettings: appSettings),
linkMetadataProvider: LinkMetadataProvider(),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
@@ -148,8 +148,6 @@ struct TimelineView_Previews: PreviewProvider { // Not testable as this preview
static let composerViewModel = ComposerToolbarViewModel.mock()
static let timelineViewModel = {
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
return TimelineViewModel(roomProxy: roomProxyMock,
timelineController: MockTimelineController(),
userSession: UserSessionMock(.init()),
@@ -157,7 +155,7 @@ struct TimelineView_Previews: PreviewProvider { // Not testable as this preview
userIndicatorController: UserIndicatorControllerMock.default,
appMediator: AppMediatorMock.default,
appSettings: appSettings,
analyticsService: analytics,
analyticsService: AnalyticsServiceMock.default(),
emojiProvider: EmojiProvider(appSettings: appSettings),
linkMetadataProvider: LinkMetadataProvider(),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
@@ -14,7 +14,7 @@ struct UserProfileScreenCoordinatorParameters {
let isPresentedModally: Bool
let userSession: UserSessionProtocol
let userIndicatorController: UserIndicatorControllerProtocol
let analytics: AnalyticsService
let analytics: AnalyticsServiceProtocol
let appSettings: AppSettings
}
@@ -15,7 +15,7 @@ typealias UserProfileScreenViewModelType = StateStoreViewModelV2<UserProfileScre
class UserProfileScreenViewModel: UserProfileScreenViewModelType, UserProfileScreenViewModelProtocol {
private let userSession: UserSessionProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let analytics: AnalyticsService
private let analytics: AnalyticsServiceProtocol
private let appSettings: AppSettings
private var actionsSubject: PassthroughSubject<UserProfileScreenViewModelAction, Never> = .init()
@@ -27,7 +27,7 @@ class UserProfileScreenViewModel: UserProfileScreenViewModelType, UserProfileScr
isPresentedModally: Bool,
userSession: UserSessionProtocol,
userIndicatorController: UserIndicatorControllerProtocol,
analytics: AnalyticsService,
analytics: AnalyticsServiceProtocol,
appSettings: AppSettings) {
self.userSession = userSession
self.userIndicatorController = userIndicatorController
@@ -143,14 +143,11 @@ struct UserProfileScreen_Previews: PreviewProvider, TestablePreview {
clientProxyMock.directRoomForUserIDReturnValue = .success("roomID")
}
let appSettings = AppSettings()
let analytics = AnalyticsService.mock(settings: appSettings)
return UserProfileScreenViewModel(userID: userID,
isPresentedModally: false,
userSession: UserSessionMock(.init(clientProxy: clientProxyMock)),
userIndicatorController: UserIndicatorControllerMock.default,
analytics: analytics,
appSettings: appSettings)
analytics: AnalyticsServiceMock.default(),
appSettings: AppSettings())
}
}
@@ -10,26 +10,11 @@ import AnalyticsEvents
import Combine
import PostHog
/// A class responsible for managing a variety of analytics clients
/// and sending events through these clients.
///
/// Events may include user activity, or app health data such as crashes,
/// non-fatal issues and performance. `Analytics` class serves as a façade
/// to all these use cases.
///
/// ## Creating Analytics Events
///
/// Events are managed in a shared repo for all Element clients https://github.com/matrix-org/matrix-analytics-events
/// To add a new event create a PR to that repo with the new/updated schema. Once merged
/// into `main`, update the AnalyticsEvents Swift package in `project.yml`.
///
class AnalyticsService {
class AnalyticsService: AnalyticsServiceProtocol {
/// The analytics client to send events with.
private let client: AnalyticsClientProtocol
private let appSettings: AppSettings
/// A signpost client for performance testing the app. This client doesn't respect the
/// `isRunning` state or behave any differently when `start`/`reset` are called.
let signpost = Signposter()
init(client: AnalyticsClientProtocol, appSettings: AppSettings) {
@@ -37,7 +22,6 @@ class AnalyticsService {
self.appSettings = appSettings
}
/// Whether to show the user the analytics opt in prompt.
var shouldShowAnalyticsPrompt: Bool {
// Only show the prompt once, and when analytics are enabled in BuildSettings.
appSettings.analyticsConsentState == .unknown && appSettings.canPromptForAnalytics
@@ -47,13 +31,11 @@ class AnalyticsService {
appSettings.analyticsConsentState == .optedIn
}
/// Opts in to analytics tracking with the supplied user session.
func optIn() {
appSettings.analyticsConsentState = .optedIn
startIfEnabled()
}
/// Stops analytics tracking and calls `reset` to clear any IDs and event queues.
func optOut() {
appSettings.analyticsConsentState = .optedOut
@@ -64,7 +46,6 @@ class AnalyticsService {
MXLog.info("Stopped.")
}
/// Starts the analytics client if the user has opted in, otherwise does nothing.
func startIfEnabled() {
guard isEnabled, !client.isRunning, let configuration = appSettings.analyticsConfiguration else { return }
@@ -76,16 +57,11 @@ class AnalyticsService {
MXLog.info("Started.")
}
/// Resets the any IDs and event queues in the analytics client. This method should
/// be called on sign-out to maintain opt-in status, whilst ensuring the next
/// account used isn't associated with the previous one.
/// Note: **MUST** be called before stopping PostHog or the reset is ignored.
func reset() {
client.reset()
MXLog.info("Reset.")
}
/// Reset the consent state for analytics
func resetConsentState() {
MXLog.warning("Resetting consent state for analytics.")
appSettings.analyticsConsentState = .unknown
@@ -104,9 +80,6 @@ class AnalyticsService {
// MARK: - Public tracking methods
extension AnalyticsService {
/// Track the presentation of a screen
/// - Parameter screen: The screen that was shown
/// - Parameter duration: An optional value representing how long the screen was shown for in milliseconds.
func track(screen: AnalyticsEvent.MobileScreen.ScreenName, duration milliseconds: Int? = nil) {
MXLog.debug("\(screen)")
let event = AnalyticsEvent.MobileScreen(durationMs: milliseconds, screenName: screen)
@@ -117,12 +90,6 @@ extension AnalyticsService {
capture(event: AnalyticsEvent.Interaction(index: index, interactionType: .Touch, name: name))
}
/// Track the presentation of a screen
/// - Parameter context: To provide additional context or description for the error
/// - Parameter domain: The domain to which the error belongs to.
/// - Parameter name: The name of the error
/// - Parameter timeToDecryptMillis: The time it took to decrypt the event in milliseconds, needs to be used only to track UTD errors, otherwise if the error is nort related to UTD it should be nil.
/// Can be found in `UnableToDecryptInfo`. In case the `UnableToDecryptInfo` contains the value as nil, pass it as `-1`
func trackError(context: String?, domain: AnalyticsEvent.Error.Domain,
name: AnalyticsEvent.Error.Name,
timeToDecryptMillis: Int? = nil,
@@ -145,19 +112,10 @@ extension AnalyticsService {
wasVisibleToUser: wasVisibleToUser))
}
/// Track the creation of a room
/// - Parameter isDM: true if the created room is a direct message, false otherwise
func trackCreatedRoom(isDM: Bool) {
capture(event: AnalyticsEvent.CreatedRoom(isDM: isDM))
}
/// Track the composer
/// - Parameters:
/// - inThread: whether the composer is used in a Thread
/// - isEditing: whether the composer is used to edit a message
/// - isReply: whether the composer is used to reply a message
/// - messageType: the type of the message
/// - startsThread: whether the composer is used to start a new thread
func trackComposer(inThread: Bool,
isEditing: Bool,
isReply: Bool,
@@ -170,19 +128,10 @@ extension AnalyticsService {
startsThread: startsThread))
}
/// Track the presentation of a room
/// - Parameters:
/// - isDM: whether the room is a direct message
/// - isSpace: whether the room is a space
func trackViewRoom(isDM: Bool, isSpace: Bool) {
capture(event: AnalyticsEvent.ViewRoom(activeSpace: nil, isDM: isDM, isSpace: isSpace, trigger: nil, viaKeyboard: nil))
}
/// Track the action of joining a room
/// - Parameters:
/// - isDM: whether the room is a direct message
/// - isSpace: whether the room is a space
/// - activeMemberCount: the number of active members in the room
func trackJoinedRoom(isDM: Bool, isSpace: Bool, activeMemberCount: UInt) {
guard let roomSize = AnalyticsEvent.JoinedRoom.RoomSize(memberCount: activeMemberCount) else {
MXLog.error("invalid room size")
@@ -191,27 +140,20 @@ extension AnalyticsService {
capture(event: AnalyticsEvent.JoinedRoom(isDM: isDM, isSpace: isSpace, roomSize: roomSize, trigger: nil))
}
/// Track the action of creating a poll
/// - Parameters:
/// - isUndisclosed: whether the poll is undisclosed
/// - numberOfAnswers: the number of options in the poll
func trackPollCreated(isUndisclosed: Bool, numberOfAnswers: Int) {
capture(event: AnalyticsEvent.PollCreation(action: .Create,
isUndisclosed: isUndisclosed,
numberOfAnswers: numberOfAnswers))
}
/// Track the action of voting on a poll
func trackPollVote() {
capture(event: AnalyticsEvent.PollVote(doNotUse: nil))
}
/// Track the action of ending a poll
func trackPollEnd() {
capture(event: AnalyticsEvent.PollEnd(doNotUse: nil))
}
/// Track a room moderation action.
func trackRoomModeration(action: AnalyticsEvent.RoomModeration.Action, role: RoomRole?) {
let role = role.map(AnalyticsEvent.RoomModeration.Role.init)
capture(event: AnalyticsEvent.RoomModeration(action: action, role: role))
@@ -0,0 +1,160 @@
//
// Copyright 2026 Element Creations Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
import AnalyticsEvents
/// A service responsible for managing analytics clients and sending events through them.
///
/// Events may include user activity, or app health data such as crashes,
/// non-fatal issues and performance.
///
/// ## Creating Analytics Events
///
/// Events are managed in a shared repo for all Element clients https://github.com/matrix-org/matrix-analytics-events
/// To add a new event create a PR to that repo with the new/updated schema. Once merged
/// into `main`, update the AnalyticsEvents Swift package in `project.yml`.
protocol AnalyticsServiceProtocol: AnyObject {
/// A signpost client for performance testing the app. This client doesn't respect the
/// `isRunning` state or behave any differently when `start`/`reset` are called.
var signpost: Signposter { get }
/// Whether to show the user the analytics opt in prompt.
var shouldShowAnalyticsPrompt: Bool { get }
/// Whether analytics tracking is currently enabled.
var isEnabled: Bool { get }
/// Opts in to analytics tracking.
func optIn()
/// Stops analytics tracking and calls `reset` to clear any IDs and event queues.
func optOut()
/// Starts the analytics client if the user has opted in, otherwise does nothing.
func startIfEnabled()
/// Resets any IDs and event queues in the analytics client. This method should
/// be called on sign-out to maintain opt-in status, whilst ensuring the next
/// account used isn't associated with the previous one.
/// Note: **MUST** be called before stopping PostHog or the reset is ignored.
func reset()
/// Resets the consent state for analytics.
func resetConsentState()
/// Track the presentation of a screen.
/// - Parameter screen: The screen that was shown.
/// - Parameter milliseconds: An optional value representing how long the screen was shown for in milliseconds.
func track(screen: AnalyticsEvent.MobileScreen.ScreenName, duration milliseconds: Int?)
func trackInteraction(index: Int?, name: AnalyticsEvent.Interaction.Name)
/// Track an analytics error event.
/// - Parameter context: Additional context or description for the error.
/// - Parameter domain: The domain to which the error belongs.
/// - Parameter name: The name of the error.
/// - Parameter timeToDecryptMillis: The time it took to decrypt the event in milliseconds. Only used for UTD errors; pass `nil` otherwise.
/// Can be found in `UnableToDecryptInfo`. If `UnableToDecryptInfo` contains `nil` for this value, pass `-1`.
func trackError(context: String?, domain: AnalyticsEvent.Error.Domain,
name: AnalyticsEvent.Error.Name,
timeToDecryptMillis: Int?,
eventLocalAgeMillis: Int?,
isFederated: Bool?,
isMatrixDotOrg: Bool?,
userTrustsOwnIdentity: Bool?,
wasVisibleToUser: Bool?)
/// Track the creation of a room.
/// - Parameter isDM: `true` if the created room is a direct message, `false` otherwise.
func trackCreatedRoom(isDM: Bool)
/// Track the composer.
/// - Parameters:
/// - inThread: Whether the composer is used in a thread.
/// - isEditing: Whether the composer is used to edit a message.
/// - isReply: Whether the composer is used to reply to a message.
/// - messageType: The type of the message.
/// - startsThread: Whether the composer is used to start a new thread.
func trackComposer(inThread: Bool,
isEditing: Bool,
isReply: Bool,
messageType: AnalyticsEvent.Composer.MessageType,
startsThread: Bool?)
/// Track the presentation of a room.
/// - Parameters:
/// - isDM: Whether the room is a direct message.
/// - isSpace: Whether the room is a space.
func trackViewRoom(isDM: Bool, isSpace: Bool)
/// Track the action of joining a room.
/// - Parameters:
/// - isDM: Whether the room is a direct message.
/// - isSpace: Whether the room is a space.
/// - activeMemberCount: The number of active members in the room.
func trackJoinedRoom(isDM: Bool, isSpace: Bool, activeMemberCount: UInt)
/// Track the action of creating a poll.
/// - Parameters:
/// - isUndisclosed: Whether the poll is undisclosed.
/// - numberOfAnswers: The number of options in the poll.
func trackPollCreated(isUndisclosed: Bool, numberOfAnswers: Int)
/// Track the action of voting on a poll.
func trackPollVote()
/// Track the action of ending a poll.
func trackPollEnd()
/// Track a room moderation action.
func trackRoomModeration(action: AnalyticsEvent.RoomModeration.Action, role: RoomRole?)
func trackSessionSecurityState(_ state: SessionSecurityState)
func updateUserProperties(_ userProperties: AnalyticsEvent.UserProperties)
func trackPinUnpinEvent(_ event: AnalyticsEvent.PinUnpinAction)
}
// sourcery: AutoMockable
extension AnalyticsServiceProtocol {
func track(screen: AnalyticsEvent.MobileScreen.ScreenName) {
track(screen: screen, duration: nil)
}
func trackInteraction(name: AnalyticsEvent.Interaction.Name) {
trackInteraction(index: nil, name: name)
}
@_disfavoredOverload // make sure this doesn't cause infinite recursion
func trackError(context: String?,
domain: AnalyticsEvent.Error.Domain,
name: AnalyticsEvent.Error.Name,
timeToDecryptMillis: Int? = nil,
eventLocalAgeMillis: Int? = nil,
isFederated: Bool? = nil,
isMatrixDotOrg: Bool? = nil,
userTrustsOwnIdentity: Bool? = nil,
wasVisibleToUser: Bool? = nil) {
trackError(context: context,
domain: domain,
name: name,
timeToDecryptMillis: timeToDecryptMillis,
eventLocalAgeMillis: eventLocalAgeMillis,
isFederated: isFederated,
isMatrixDotOrg: isMatrixDotOrg,
userTrustsOwnIdentity: userTrustsOwnIdentity,
wasVisibleToUser: wasVisibleToUser)
}
func trackComposer(inThread: Bool,
isEditing: Bool,
isReply: Bool,
startsThread: Bool?) {
trackComposer(inThread: inThread,
isEditing: isEditing,
isReply: isReply,
messageType: .Text,
startsThread: startsThread)
}
}
@@ -9,5 +9,5 @@
import SwiftUI
extension EnvironmentValues {
@Entry var analyticsService: AnalyticsService?
@Entry var analyticsService: AnalyticsServiceProtocol?
}
@@ -17,7 +17,7 @@ class ClientProxy: ClientProxyProtocol {
private let client: ClientProtocol
private let networkMonitor: NetworkMonitorProtocol
private let appSettings: AppSettings
private let analyticsService: AnalyticsService
private let analyticsService: AnalyticsServiceProtocol
let mediaLoader: MediaLoaderProtocol
private let clientQueue: DispatchQueue
@@ -202,7 +202,7 @@ class ClientProxy: ClientProxyProtocol {
init(client: ClientProtocol,
networkMonitor: NetworkMonitorProtocol,
appSettings: AppSettings,
analyticsService: AnalyticsService) async throws {
analyticsService: AnalyticsServiceProtocol) async throws {
self.client = client
self.networkMonitor = networkMonitor
self.appSettings = appSettings
@@ -9,10 +9,10 @@
import Foundation
class PollInteractionHandler: PollInteractionHandlerProtocol {
let analyticsService: AnalyticsService
let analyticsService: AnalyticsServiceProtocol
let timelineController: TimelineControllerProtocol
init(analyticsService: AnalyticsService, timelineController: TimelineControllerProtocol) {
init(analyticsService: AnalyticsServiceProtocol, timelineController: TimelineControllerProtocol) {
self.analyticsService = analyticsService
self.timelineController = timelineController
}
@@ -15,7 +15,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
private let roomListService: RoomListServiceProtocol
private let room: RoomProtocol
private let appSettings: AppSettings
private let analyticsService: AnalyticsService
private let analyticsService: AnalyticsServiceProtocol
private let eventStringBuilder: RoomEventStringBuilder
// periphery:ignore - required for instance retention in the rust codebase
@@ -79,7 +79,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
init(roomListService: RoomListServiceProtocol,
room: RoomProtocol,
appSettings: AppSettings,
analyticsService: AnalyticsService,
analyticsService: AnalyticsServiceProtocol,
eventStringBuilder: RoomEventStringBuilder) async throws {
self.roomListService = roomListService
self.room = room
@@ -12,7 +12,7 @@ import MatrixRustSDK
class UserSessionStore: UserSessionStoreProtocol {
private let keychainController: KeychainControllerProtocol
private let appSettings: AppSettings
private let analyticsService: AnalyticsService
private let analyticsService: AnalyticsServiceProtocol
private let networkMonitor: NetworkMonitorProtocol
private let appHooks: AppHooks
@@ -32,7 +32,7 @@ class UserSessionStore: UserSessionStoreProtocol {
init(keychainController: KeychainControllerProtocol,
appSettings: AppSettings,
analyticsService: AnalyticsService,
analyticsService: AnalyticsServiceProtocol,
appHooks: AppHooks,
networkMonitor: NetworkMonitorProtocol) {
self.keychainController = keychainController
@@ -13,7 +13,7 @@ import UIKit
class UITestsAppCoordinator: AppCoordinatorProtocol, SecureWindowManagerDelegate {
private let appSettings: AppSettings
private let analytics: AnalyticsService
private let analytics: AnalyticsServiceProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let navigationRootCoordinator: NavigationRootCoordinator
@@ -42,7 +42,7 @@ class UITestsAppCoordinator: AppCoordinatorProtocol, SecureWindowManagerDelegate
let analyticsClient = AnalyticsClientMock()
analyticsClient.isRunning = false
analytics = .mock(analyticsClient, settings: appSettings)
analytics = AnalyticsServiceMock.default()
userIndicatorController = UserIndicatorController()
windowManager.delegate = self
@@ -115,7 +115,7 @@ class MockScreen: Identifiable {
let navigationRootCoordinator: NavigationRootCoordinator
private let appSettings: AppSettings
private let analytics: AnalyticsService
private let analytics: AnalyticsServiceProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private var client: UITestsSignalling.Client?
@@ -127,7 +127,7 @@ class MockScreen: Identifiable {
windowManager: SecureWindowManagerProtocol,
navigationRootCoordinator: NavigationRootCoordinator,
appSettings: AppSettings,
analytics: AnalyticsService,
analytics: AnalyticsServiceProtocol,
userIndicatorController: UserIndicatorControllerProtocol) {
self.id = id
self.windowManager = windowManager