diff --git a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved index 9870eb6da0..33a49b2c61 100644 --- a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/matrix-org/matrix-analytics-events", "state" : { - "revision" : "2f5fa5f1e2f6c6ae1a47c33d953a3ce289167eb0", - "version" : "0.5.0" + "revision" : "b0f4314ce1215ae5741a95e1eebd24ba6108d43e" } }, { diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index ce49740834..0604d86492 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -97,21 +97,21 @@ internal class Asset: NSObject { internal static let touchidIcon = ImageAsset(name: "touchid_icon") internal static let addGroupParticipant = ImageAsset(name: "add_group_participant") internal static let removeIconBlue = ImageAsset(name: "remove_icon_blue") - internal static let bold = ImageAsset(name: "Bold") - internal static let code = ImageAsset(name: "Code") internal static let indentIncrease = ImageAsset(name: "Indent_increase") - internal static let italic = ImageAsset(name: "Italic") - internal static let link = ImageAsset(name: "Link") - internal static let numberedList = ImageAsset(name: "Numbered list") - internal static let quote = ImageAsset(name: "Quote") - internal static let strikethrough = ImageAsset(name: "Strikethrough") - internal static let underlined = ImageAsset(name: "Underlined") + internal static let bold = ImageAsset(name: "bold") internal static let bulletList = ImageAsset(name: "bullet_list") + internal static let code = ImageAsset(name: "code") internal static let codeBlock = ImageAsset(name: "code_block") internal static let indentDecrease = ImageAsset(name: "indent_decrease") + internal static let italic = ImageAsset(name: "italic") + internal static let link = ImageAsset(name: "link") internal static let maximiseComposer = ImageAsset(name: "maximise_composer") internal static let minimiseComposer = ImageAsset(name: "minimise_composer") + internal static let numberedList = ImageAsset(name: "numbered list") + internal static let quote = ImageAsset(name: "quote") internal static let startComposeModule = ImageAsset(name: "start_compose_module") + internal static let strikethrough = ImageAsset(name: "strikethrough") + internal static let underlined = ImageAsset(name: "underlined") internal static let findYourContactsFacepile = ImageAsset(name: "find_your_contacts_facepile") internal static let captureAvatar = ImageAsset(name: "capture_avatar") internal static let deleteAvatar = ImageAsset(name: "delete_avatar") diff --git a/Riot/Modules/Analytics/Analytics.swift b/Riot/Modules/Analytics/Analytics.swift index 1a30841b98..e9efbee86d 100644 --- a/Riot/Modules/Analytics/Analytics.swift +++ b/Riot/Modules/Analytics/Analytics.swift @@ -382,17 +382,82 @@ extension Analytics: MXAnalyticsDelegate { // Do we still want to track this? } - func trackComposerEvent(inThread: Bool, isEditing: Bool, isReply: Bool, startsThread: Bool) { - let event = AnalyticsEvent.Composer(inThread: inThread, - isEditing: isEditing, - isReply: isReply, - startsThread: startsThread) + func trackComposerEvent( + inThread: Bool, + isEditing: Bool, + isReply: Bool, + startsThread: Bool + ) { + var editor: AnalyticsEvent.Composer.Editor + if !RiotSettings.shared.enableWysiwygComposer { + editor = .Legacy + } else if RiotSettings.shared.enableWysiwygTextFormatting { + editor = .RteFormatting + } else { + editor = .RtePlain + } + // The legacy editor always has markdown enabled as does the new editor in plain text mode + let markDownEnabled = !RiotSettings.shared.enableWysiwygComposer || !RiotSettings.shared.enableWysiwygTextFormatting + let event = AnalyticsEvent.Composer( + editor: editor, + inThread: inThread, + isEditing: isEditing, + isMarkdownEnabled: markDownEnabled, + isReply: isReply, + startsThread: startsThread + ) + capture(event: event) + } + + func trackFormattedMessageEvent( + formatAction: AnalyticsEvent.FormattedMessage.FormatAction + ) { + var editor: AnalyticsEvent.FormattedMessage.Editor + if !RiotSettings.shared.enableWysiwygComposer { + editor = .Legacy + } else if RiotSettings.shared.enableWysiwygTextFormatting { + editor = .RteFormatting + } else { + editor = .RtePlain + } + let event = AnalyticsEvent.FormattedMessage(editor: editor, formatAction: formatAction) + capture(event: event) + } + + func trackMentionEvent( + targetType: AnalyticsEvent.Mention.TargetType + ) { + var editor: AnalyticsEvent.Mention.Editor + if !RiotSettings.shared.enableWysiwygComposer { + editor = .Legacy + } else if RiotSettings.shared.enableWysiwygTextFormatting { + editor = .RteFormatting + } else { + editor = .RtePlain + } + let event = AnalyticsEvent.Mention(editor: editor, targetType: targetType) + capture(event: event) + } + + func trackSlashCommandEvent( + command: AnalyticsEvent.SlashCommand.Command + ) { + var editor: AnalyticsEvent.SlashCommand.Editor + if !RiotSettings.shared.enableWysiwygComposer { + editor = .Legacy + } else if RiotSettings.shared.enableWysiwygTextFormatting { + editor = .RteFormatting + } else { + editor = .RtePlain + } + let event = AnalyticsEvent.SlashCommand(command: command, editor: editor) capture(event: event) } func trackNonFatalIssue(_ issue: String, details: [String: Any]?) { monitoringClient.trackNonFatalIssue(issue, details: details) } + } /// iOS-specific analytics event triggered when users select the Crypto SDK labs option diff --git a/Riot/Modules/MatrixKit/Models/Room/MXKSlashCommands.swift b/Riot/Modules/MatrixKit/Models/Room/MXKSlashCommands.swift index 54ab1ab3c5..1fbe0b4da2 100644 --- a/Riot/Modules/MatrixKit/Models/Room/MXKSlashCommands.swift +++ b/Riot/Modules/MatrixKit/Models/Room/MXKSlashCommands.swift @@ -14,6 +14,8 @@ // limitations under the License. // +import AnalyticsEvents + @objc final class MXKSlashCommandsHelper: NSObject { @objc static func commandNameFor(_ slashCommand: MXKSlashCommand) -> String { slashCommand.cmd @@ -98,4 +100,15 @@ return "" } } + + var analyticsCommand: AnalyticsEvent.SlashCommand.Command? { + switch self { + case .inviteUser: + return .Invite + case .partRoom: + return .Part + default: + return nil + } + } } diff --git a/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift b/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift index ad488897fc..0c8c14c8c4 100644 --- a/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift +++ b/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift @@ -226,12 +226,13 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp completionSuggestionSharedContext: toolbarViewDelegate.completionSuggestionContext().context, resizeAnimationDuration: Double(kResizeComposerAnimationDuration), sendMessageAction: { [weak self] content in - guard let self = self else { return } - self.sendWysiwygMessage(content: content) - }, showSendMediaActions: { [weak self] in - guard let self = self else { return } - self.showSendMediaActions() - }) + guard let self = self else { return } + self.sendWysiwygMessage(content: content) + }, + showSendMediaActions: { [weak self] in + guard let self = self else { return } + self.showSendMediaActions() + }) .introspectTextView { [weak self] textView in guard let self = self else { return } textView.inputAccessoryView = self.inputAccessoryViewForKeyboard @@ -381,6 +382,8 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp toolbarViewDelegate?.didSendLinkAction(LinkActionWrapper(linkAction)) case let .suggestion(pattern): toolbarViewDelegate?.didDetectTextPattern(SuggestionPatternWrapper(pattern)) + case let .messageFormatted(formatType): + Analytics.shared.trackFormattedMessageEvent(formatAction: formatType.analyticsAction) } } diff --git a/RiotSwiftUI/Modules/Room/CompletionSuggestion/Coordinator/CompletionSuggestionCoordinator.swift b/RiotSwiftUI/Modules/Room/CompletionSuggestion/Coordinator/CompletionSuggestionCoordinator.swift index 4196da77a8..45fd4b7312 100644 --- a/RiotSwiftUI/Modules/Room/CompletionSuggestion/Coordinator/CompletionSuggestionCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/CompletionSuggestion/Coordinator/CompletionSuggestionCoordinator.swift @@ -89,13 +89,18 @@ final class CompletionSuggestionCoordinator: Coordinator, Presentable { switch result { case .selectedItemWithIdentifier(let identifier): if identifier == CompletionSuggestionUserID.room { + Analytics.shared.trackMentionEvent(targetType: .Room) self.delegate?.completionSuggestionCoordinatorDidRequestMentionForRoom(self, textTrigger: self.completionSuggestionService.currentTextTrigger) return } if let member = self.roomMemberProvider.roomMembers.filter({ $0.userId == identifier }).first { + Analytics.shared.trackMentionEvent(targetType: .User) self.delegate?.completionSuggestionCoordinator(self, didRequestMentionForMember: member, textTrigger: self.completionSuggestionService.currentTextTrigger) } else if let command = self.commandProvider.commands.filter({ $0.cmd == identifier }).first { + if let analyticsCommand = command.analyticsCommand { + Analytics.shared.trackSlashCommandEvent(command: analyticsCommand) + } self.delegate?.completionSuggestionCoordinator(self, didRequestCommand: command.cmd, textTrigger: self.completionSuggestionService.currentTextTrigger) } } diff --git a/RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift b/RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift index 33d73ef4a2..6381b49fe3 100644 --- a/RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift +++ b/RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift @@ -17,6 +17,7 @@ import Foundation import SwiftUI import WysiwygComposer +import AnalyticsEvents // MARK: View @@ -215,6 +216,35 @@ extension FormatType { return .link } } + + var analyticsAction: AnalyticsEvent.FormattedMessage.FormatAction { + switch self { + case .bold: + return .Bold + case .italic: + return .Italic + case .underline: + return .Underline + case .strikethrough: + return .Strikethrough + case .unorderedList: + return .UnorderedList + case .orderedList: + return .OrderedList + case .indent: + return .Indent + case .unindent: + return .Unindent + case .inlineCode: + return .InlineCode + case .codeBlock: + return .CodeBlock + case .quote: + return .Quote + case .link: + return .Link + } + } } enum ComposerSendMode: Equatable { @@ -230,6 +260,7 @@ enum ComposerViewAction: Equatable { case linkTapped(linkAction: LinkAction) case storeSelection(selection: NSRange) case suggestion(pattern: SuggestionPattern?) + case messageFormatted(formatType: FormatType) } enum ComposerViewModelResult: Equatable { @@ -237,6 +268,8 @@ enum ComposerViewModelResult: Equatable { case contentDidChange(isEmpty: Bool) case linkTapped(LinkAction: LinkAction) case suggestion(pattern: SuggestionPattern?) + case messageFormatted(formatType: FormatType) + case mentionSelected(formatType: FormatType) } final class LinkActionWrapper: NSObject { diff --git a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift index a74b0bb4d6..417f9a5931 100644 --- a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift +++ b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift @@ -233,6 +233,7 @@ struct Composer: View { self.resizeAnimationDuration = resizeAnimationDuration self.sendMessageAction = sendMessageAction self.showSendMediaActions = showSendMediaActions + } var body: some View { @@ -264,6 +265,7 @@ struct Composer: View { HStack(alignment: .center, spacing: 0) { sendMediaButton FormattingToolbar(formatItems: formatItems) { type in + viewModel.send(viewAction: .messageFormatted(formatType: type)) if type.action == .link { storeCurrentSelection() sendLinkAction() diff --git a/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift b/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift index 6448b9de33..1668a98cd9 100644 --- a/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift +++ b/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift @@ -15,6 +15,7 @@ // import SwiftUI +import AnalyticsEvents typealias ComposerViewModelType = StateStoreViewModel @@ -92,6 +93,8 @@ final class ComposerViewModel: ComposerViewModelType, ComposerViewModelProtocol selectionToRestore = selection case let .suggestion(pattern: pattern): callback?(.suggestion(pattern: pattern)) + case let .messageFormatted(formatType: formatType): + callback?(.messageFormatted(formatType: formatType)) } } diff --git a/project.yml b/project.yml index 6a207706dd..bb113d84c6 100644 --- a/project.yml +++ b/project.yml @@ -42,7 +42,9 @@ include: packages: AnalyticsEvents: url: https://github.com/matrix-org/matrix-analytics-events - exactVersion: 0.5.0 +# TODO: bump version and remove revision before un-drafting PR. +# exactVersion: 0.5.0 + revision: b0f4314ce1215ae5741a95e1eebd24ba6108d43e Mapbox: url: https://github.com/maplibre/maplibre-gl-native-distribution minVersion: 5.12.2