mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-06 04:18:31 +00:00
Allow the services that are configured by secrets to be disabled. (#3961)
* Make the map tiler key optional. * Make the bug report URL optional. * Make the sentry URL optional. * Make the analytics configuration optional and handle consent taking Sentry into account. * Stop prompting users to report crashes when Sentry is disabled.
This commit is contained in:
@@ -786,6 +786,7 @@
|
|||||||
9A0326D2375075871D2AB537 /* ResolveVerifiedUserSendFailureScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 574CB70E82D7EAEA538E4135 /* ResolveVerifiedUserSendFailureScreenViewModel.swift */; };
|
9A0326D2375075871D2AB537 /* ResolveVerifiedUserSendFailureScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 574CB70E82D7EAEA538E4135 /* ResolveVerifiedUserSendFailureScreenViewModel.swift */; };
|
||||||
9A3B0CDF097E3838FB1B9595 /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E89E530A8E92EC44301CA1 /* Bundle.swift */; };
|
9A3B0CDF097E3838FB1B9595 /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E89E530A8E92EC44301CA1 /* Bundle.swift */; };
|
||||||
9A4E3D5AA44B041DAC3A0D81 /* OIDCAuthenticationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92390F9FA98255440A6BF5F8 /* OIDCAuthenticationPresenter.swift */; };
|
9A4E3D5AA44B041DAC3A0D81 /* OIDCAuthenticationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92390F9FA98255440A6BF5F8 /* OIDCAuthenticationPresenter.swift */; };
|
||||||
|
9A8E6FCD86B89970EC72EFD8 /* BugReportServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F7FC9580CABF797A2E6213A /* BugReportServiceMock.swift */; };
|
||||||
9AC5F8142413862A9E3A2D98 /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 531CE4334AC5CA8DFF6AEB84 /* DTCoreText */; };
|
9AC5F8142413862A9E3A2D98 /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 531CE4334AC5CA8DFF6AEB84 /* DTCoreText */; };
|
||||||
9B03943616A1147539DF7F08 /* RoomChangePermissionsScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41D041A857614A9AE13C7795 /* RoomChangePermissionsScreenViewModelTests.swift */; };
|
9B03943616A1147539DF7F08 /* RoomChangePermissionsScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41D041A857614A9AE13C7795 /* RoomChangePermissionsScreenViewModelTests.swift */; };
|
||||||
9B356742E035D90A8BB5CABE /* ProposedViewSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DFE0E493FB55E5A62E7852A /* ProposedViewSize.swift */; };
|
9B356742E035D90A8BB5CABE /* ProposedViewSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DFE0E493FB55E5A62E7852A /* ProposedViewSize.swift */; };
|
||||||
@@ -2029,6 +2030,7 @@
|
|||||||
8F062DD2CCD95DC33528A16F /* KnockRequestProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KnockRequestProxy.swift; sourceTree = "<group>"; };
|
8F062DD2CCD95DC33528A16F /* KnockRequestProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KnockRequestProxy.swift; sourceTree = "<group>"; };
|
||||||
8F21ED7205048668BEB44A38 /* AppActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppActivityView.swift; sourceTree = "<group>"; };
|
8F21ED7205048668BEB44A38 /* AppActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppActivityView.swift; sourceTree = "<group>"; };
|
||||||
8F6210134203BE1F2DD5C679 /* RoomDirectoryCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectoryCell.swift; sourceTree = "<group>"; };
|
8F6210134203BE1F2DD5C679 /* RoomDirectoryCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectoryCell.swift; sourceTree = "<group>"; };
|
||||||
|
8F7FC9580CABF797A2E6213A /* BugReportServiceMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportServiceMock.swift; sourceTree = "<group>"; };
|
||||||
8F841F219ACDFC1D3F42FEFB /* RoomChangeRolesScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangeRolesScreenViewModelTests.swift; sourceTree = "<group>"; };
|
8F841F219ACDFC1D3F42FEFB /* RoomChangeRolesScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangeRolesScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||||
8FB89DC7F9A4A91020037001 /* AuthenticationStartScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationStartScreenViewModelTests.swift; sourceTree = "<group>"; };
|
8FB89DC7F9A4A91020037001 /* AuthenticationStartScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationStartScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||||
8FC803282F9268D49F4ABF14 /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = "<group>"; };
|
8FC803282F9268D49F4ABF14 /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = "<group>"; };
|
||||||
@@ -3215,6 +3217,7 @@
|
|||||||
0554FEA301486A8CFA475D5A /* AuthenticationClientBuilderFactoryMock.swift */,
|
0554FEA301486A8CFA475D5A /* AuthenticationClientBuilderFactoryMock.swift */,
|
||||||
4760CE2128FBC217304272AB /* AuthenticationClientBuilderMock.swift */,
|
4760CE2128FBC217304272AB /* AuthenticationClientBuilderMock.swift */,
|
||||||
9FD7E851E2BA8C5A8D284B2A /* BannedRoomProxyMock.swift */,
|
9FD7E851E2BA8C5A8D284B2A /* BannedRoomProxyMock.swift */,
|
||||||
|
8F7FC9580CABF797A2E6213A /* BugReportServiceMock.swift */,
|
||||||
E2F96CCBEAAA7F2185BFA354 /* ClientProxyMock.swift */,
|
E2F96CCBEAAA7F2185BFA354 /* ClientProxyMock.swift */,
|
||||||
4E600B315B920B9687F8EE1B /* ComposerDraftServiceMock.swift */,
|
4E600B315B920B9687F8EE1B /* ComposerDraftServiceMock.swift */,
|
||||||
E321E840DCC63790049984F4 /* ElementCallServiceMock.swift */,
|
E321E840DCC63790049984F4 /* ElementCallServiceMock.swift */,
|
||||||
@@ -6947,6 +6950,7 @@
|
|||||||
8D71E5E53F372202379BECCE /* BugReportScreenViewModel.swift in Sources */,
|
8D71E5E53F372202379BECCE /* BugReportScreenViewModel.swift in Sources */,
|
||||||
B4A0C69370E6008A971463E7 /* BugReportScreenViewModelProtocol.swift in Sources */,
|
B4A0C69370E6008A971463E7 /* BugReportScreenViewModelProtocol.swift in Sources */,
|
||||||
3DA57CA0D609A6B37CA1DC2F /* BugReportService.swift in Sources */,
|
3DA57CA0D609A6B37CA1DC2F /* BugReportService.swift in Sources */,
|
||||||
|
9A8E6FCD86B89970EC72EFD8 /* BugReportServiceMock.swift in Sources */,
|
||||||
172E6E9A612ADCF10A62CF13 /* BugReportServiceProtocol.swift in Sources */,
|
172E6E9A612ADCF10A62CF13 /* BugReportServiceProtocol.swift in Sources */,
|
||||||
E1DF24D085572A55C9758A2D /* Bundle.swift in Sources */,
|
E1DF24D085572A55C9758A2D /* Bundle.swift in Sources */,
|
||||||
6BAD956B909A6E29F6CC6E7C /* ButtonStyle.swift in Sources */,
|
6BAD956B909A6E29F6CC6E7C /* ButtonStyle.swift in Sources */,
|
||||||
|
|||||||
@@ -355,8 +355,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
|||||||
private static func setupServiceLocator(appSettings: AppSettings, appHooks: AppHooks) {
|
private static func setupServiceLocator(appSettings: AppSettings, appHooks: AppHooks) {
|
||||||
ServiceLocator.shared.register(userIndicatorController: UserIndicatorController())
|
ServiceLocator.shared.register(userIndicatorController: UserIndicatorController())
|
||||||
ServiceLocator.shared.register(appSettings: appSettings)
|
ServiceLocator.shared.register(appSettings: appSettings)
|
||||||
ServiceLocator.shared.register(bugReportService: BugReportService(withBaseURL: appSettings.bugReportServiceBaseURL,
|
ServiceLocator.shared.register(bugReportService: BugReportService(baseURL: appSettings.bugReportServiceBaseURL,
|
||||||
applicationId: appSettings.bugReportApplicationId,
|
applicationID: appSettings.bugReportApplicationID,
|
||||||
sdkGitSHA: sdkGitSha(),
|
sdkGitSHA: sdkGitSha(),
|
||||||
maxUploadSize: appSettings.bugReportMaxUploadSize,
|
maxUploadSize: appSettings.bugReportMaxUploadSize,
|
||||||
appHooks: appHooks))
|
appHooks: appHooks))
|
||||||
@@ -782,6 +782,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static func setupSentry(appSettings: AppSettings) {
|
private static func setupSentry(appSettings: AppSettings) {
|
||||||
|
guard let bugReportSentryURL = appSettings.bugReportSentryURL else { return }
|
||||||
|
|
||||||
let options: Options = .init()
|
let options: Options = .init()
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
@@ -790,7 +792,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
|||||||
options.enabled = appSettings.analyticsConsentState == .optedIn
|
options.enabled = appSettings.analyticsConsentState == .optedIn
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
options.dsn = appSettings.bugReportSentryURL.absoluteString
|
options.dsn = bugReportSentryURL.absoluteString
|
||||||
|
|
||||||
if AppSettings.isDevelopmentBuild {
|
if AppSettings.isDevelopmentBuild {
|
||||||
options.environment = "development"
|
options.environment = "development"
|
||||||
|
|||||||
@@ -223,23 +223,21 @@ final class AppSettings {
|
|||||||
|
|
||||||
// MARK: - Bug report
|
// MARK: - Bug report
|
||||||
|
|
||||||
let bugReportServiceBaseURL: URL! = URL(string: Secrets.rageshakeServerURL)
|
let bugReportServiceBaseURL: URL? = URL(string: Secrets.rageshakeServerURL)! // swiftlint:disable:this force_unwrapping
|
||||||
let bugReportSentryURL: URL! = URL(string: Secrets.sentryDSN)
|
let bugReportSentryURL: URL? = URL(string: Secrets.sentryDSN)! // swiftlint:disable:this force_unwrapping
|
||||||
// Use the name allocated by the bug report server
|
/// The name allocated by the bug report server
|
||||||
let bugReportApplicationId = "element-x-ios"
|
let bugReportApplicationID = "element-x-ios"
|
||||||
/// The maximum size of the upload request. Default value is just below CloudFlare's max request size.
|
/// The maximum size of the upload request. Default value is just below CloudFlare's max request size.
|
||||||
let bugReportMaxUploadSize = 50 * 1024 * 1024
|
let bugReportMaxUploadSize = 50 * 1024 * 1024
|
||||||
|
|
||||||
// MARK: - Analytics
|
// MARK: - Analytics
|
||||||
|
|
||||||
/// The configuration to use for analytics. Set `isEnabled` to false to disable analytics.
|
/// The configuration to use for analytics. Set to `nil` to disable analytics.
|
||||||
///
|
let analyticsConfiguration: AnalyticsConfiguration? = AnalyticsConfiguration(host: Secrets.postHogHost, apiKey: Secrets.postHogAPIKey)
|
||||||
/// **Note:** Analytics are disabled by default for forks. If you are maintaining a fork you will
|
/// The URL to open with more information about analytics terms. When this is `nil` the "Learn more" link will be hidden.
|
||||||
/// need to regenerate the Secrets file with your PostHog server and API key before enabling.
|
private(set) var analyticsTermsURL: URL? = "https://element.io/cookie-policy"
|
||||||
let analyticsConfiguration = AnalyticsConfiguration(isEnabled: InfoPlistReader.main.bundleIdentifier.starts(with: "io.element."),
|
/// Whether or not there the app is able ask for user consent to enable analytics or sentry reporting.
|
||||||
host: Secrets.postHogHost,
|
var canPromptForAnalytics: Bool { analyticsConfiguration != nil || bugReportSentryURL != nil }
|
||||||
apiKey: Secrets.postHogAPIKey,
|
|
||||||
termsURL: "https://element.io/cookie-policy")
|
|
||||||
|
|
||||||
/// Whether the user has opted in to send analytics.
|
/// Whether the user has opted in to send analytics.
|
||||||
@UserPreference(key: UserDefaultsKeys.analyticsConsentState, defaultValue: AnalyticsConsentState.unknown, storageType: .userDefaults(store))
|
@UserPreference(key: UserDefaultsKeys.analyticsConsentState, defaultValue: AnalyticsConsentState.unknown, storageType: .userDefaults(store))
|
||||||
|
|||||||
@@ -68,7 +68,8 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func showStartScreen() {
|
private func showStartScreen() {
|
||||||
let parameters = AuthenticationStartScreenParameters(webRegistrationEnabled: appSettings.webRegistrationEnabled)
|
let parameters = AuthenticationStartScreenParameters(webRegistrationEnabled: appSettings.webRegistrationEnabled,
|
||||||
|
isBugReportServiceEnabled: bugReportService.isEnabled)
|
||||||
let coordinator = AuthenticationStartScreenCoordinator(parameters: parameters)
|
let coordinator = AuthenticationStartScreenCoordinator(parameters: parameters)
|
||||||
|
|
||||||
coordinator.actions
|
coordinator.actions
|
||||||
|
|||||||
@@ -379,7 +379,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func presentAnalyticsPromptScreen() {
|
private func presentAnalyticsPromptScreen() {
|
||||||
let coordinator = AnalyticsPromptScreenCoordinator(analytics: analyticsService, termsURL: appSettings.analyticsConfiguration.termsURL)
|
let coordinator = AnalyticsPromptScreenCoordinator(analytics: analyticsService, termsURL: appSettings.analyticsTermsURL)
|
||||||
|
|
||||||
coordinator.actions
|
coordinator.actions
|
||||||
.sink { [weak self] action in
|
.sink { [weak self] action in
|
||||||
|
|||||||
@@ -86,7 +86,8 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
navigationStackCoordinator = NavigationStackCoordinator()
|
navigationStackCoordinator = NavigationStackCoordinator()
|
||||||
|
|
||||||
let settingsScreenCoordinator = SettingsScreenCoordinator(parameters: .init(userSession: parameters.userSession,
|
let settingsScreenCoordinator = SettingsScreenCoordinator(parameters: .init(userSession: parameters.userSession,
|
||||||
appSettings: parameters.appSettings))
|
appSettings: parameters.appSettings,
|
||||||
|
isBugReportServiceEnabled: parameters.bugReportService.isEnabled))
|
||||||
|
|
||||||
settingsScreenCoordinator.actions
|
settingsScreenCoordinator.actions
|
||||||
.sink { [weak self] action in
|
.sink { [weak self] action in
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2025 New Vector Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
// Please see LICENSE files in the repository root for full details.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension BugReportServiceMock {
|
||||||
|
struct Configuration {
|
||||||
|
var isEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
convenience init(_ configuration: Configuration) {
|
||||||
|
self.init()
|
||||||
|
|
||||||
|
isEnabled = configuration.isEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2130,6 +2130,11 @@ class BannedRoomProxyMock: BannedRoomProxyProtocol, @unchecked Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
class BugReportServiceMock: BugReportServiceProtocol, @unchecked Sendable {
|
class BugReportServiceMock: BugReportServiceProtocol, @unchecked Sendable {
|
||||||
|
var isEnabled: Bool {
|
||||||
|
get { return underlyingIsEnabled }
|
||||||
|
set(value) { underlyingIsEnabled = value }
|
||||||
|
}
|
||||||
|
var underlyingIsEnabled: Bool!
|
||||||
var crashedLastRun: Bool {
|
var crashedLastRun: Bool {
|
||||||
get { return underlyingCrashedLastRun }
|
get { return underlyingCrashedLastRun }
|
||||||
set(value) { underlyingCrashedLastRun = value }
|
set(value) { underlyingCrashedLastRun = value }
|
||||||
|
|||||||
@@ -13,17 +13,21 @@ import CoreLocation
|
|||||||
/// [FORKING.md](https://github.com/element-hq/element-x-ios/blob/develop/docs/FORKING.md#setup-the-location-sharing)
|
/// [FORKING.md](https://github.com/element-hq/element-x-ios/blob/develop/docs/FORKING.md#setup-the-location-sharing)
|
||||||
struct MapTilerConfiguration {
|
struct MapTilerConfiguration {
|
||||||
let baseURL: URL
|
let baseURL: URL
|
||||||
let apiKey: String
|
/// The API key for fetching map tiles. When not set, location sharing will be disabled and
|
||||||
|
/// any received locations will be shown in the timeline with a generic blurred map image.
|
||||||
|
let apiKey: String?
|
||||||
/// A MapLibre style ID for a light-mode map.
|
/// A MapLibre style ID for a light-mode map.
|
||||||
let lightStyleID: String
|
let lightStyleID: String
|
||||||
/// A MapLibre style ID for a dark-mode map.
|
/// A MapLibre style ID for a dark-mode map.
|
||||||
let darkStyleID: String
|
let darkStyleID: String
|
||||||
|
|
||||||
|
var isEnabled: Bool { apiKey != nil }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MapTilerConfiguration: MapTilerURLBuilderProtocol {
|
extension MapTilerConfiguration: MapTilerURLBuilderProtocol {
|
||||||
func dynamicMapURL(for style: MapTilerStyle) -> URL? {
|
func dynamicMapURL(for style: MapTilerStyle) -> URL? {
|
||||||
var url = makeNewURL(for: style)
|
var url = makeNewURL(for: style)
|
||||||
url.appendPathComponent("style.json", conformingTo: .json)
|
url?.appendPathComponent("style.json", conformingTo: .json)
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,20 +37,22 @@ extension MapTilerConfiguration: MapTilerURLBuilderProtocol {
|
|||||||
size: CGSize,
|
size: CGSize,
|
||||||
attribution: MapTilerAttributionPlacement) -> URL? {
|
attribution: MapTilerAttributionPlacement) -> URL? {
|
||||||
var url = makeNewURL(for: style)
|
var url = makeNewURL(for: style)
|
||||||
url.appendPathComponent(String(format: "static/%f,%f,%f/%dx%d@2x.png",
|
url?.appendPathComponent(String(format: "static/%f,%f,%f/%dx%d@2x.png",
|
||||||
coordinates.longitude,
|
coordinates.longitude,
|
||||||
coordinates.latitude,
|
coordinates.latitude,
|
||||||
zoomLevel,
|
zoomLevel,
|
||||||
Int(size.width),
|
Int(size.width),
|
||||||
Int(size.height)),
|
Int(size.height)),
|
||||||
conformingTo: .png)
|
conformingTo: .png)
|
||||||
url.append(queryItems: [.init(name: "attribution", value: attribution.rawValue)])
|
url?.append(queryItems: [.init(name: "attribution", value: attribution.rawValue)])
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
private func makeNewURL(for style: MapTilerStyle) -> URL {
|
private func makeNewURL(for style: MapTilerStyle) -> URL? {
|
||||||
|
guard let apiKey else { return nil }
|
||||||
|
|
||||||
var url: URL = baseURL
|
var url: URL = baseURL
|
||||||
url.appendPathComponent(styleID(for: style), conformingTo: .item)
|
url.appendPathComponent(styleID(for: style), conformingTo: .item)
|
||||||
url.append(queryItems: [URLQueryItem(name: "key", value: apiKey)])
|
url.append(queryItems: [URLQueryItem(name: "key", value: apiKey)])
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ private struct VisualListItemLabelStyle: LabelStyle {
|
|||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
||||||
struct VisualListItem_Previews: PreviewProvider, TestablePreview {
|
struct VisualListItem_Previews: PreviewProvider, TestablePreview {
|
||||||
static let strings = AnalyticsPromptScreenStrings(termsURL: ServiceLocator.shared.settings.analyticsConfiguration.termsURL)
|
static let strings = AnalyticsPromptScreenStrings(termsURL: ServiceLocator.shared.settings.analyticsTermsURL)
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
static var testImage1: some View {
|
static var testImage1: some View {
|
||||||
|
|||||||
+3
-1
@@ -10,6 +10,7 @@ import SwiftUI
|
|||||||
|
|
||||||
struct AuthenticationStartScreenParameters {
|
struct AuthenticationStartScreenParameters {
|
||||||
let webRegistrationEnabled: Bool
|
let webRegistrationEnabled: Bool
|
||||||
|
let isBugReportServiceEnabled: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
final class AuthenticationStartScreenCoordinator: CoordinatorProtocol {
|
final class AuthenticationStartScreenCoordinator: CoordinatorProtocol {
|
||||||
@@ -22,7 +23,8 @@ final class AuthenticationStartScreenCoordinator: CoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init(parameters: AuthenticationStartScreenParameters) {
|
init(parameters: AuthenticationStartScreenParameters) {
|
||||||
viewModel = AuthenticationStartScreenViewModel(webRegistrationEnabled: parameters.webRegistrationEnabled)
|
viewModel = AuthenticationStartScreenViewModel(webRegistrationEnabled: parameters.webRegistrationEnabled,
|
||||||
|
isBugReportServiceEnabled: parameters.isBugReportServiceEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|||||||
+1
@@ -26,6 +26,7 @@ enum AuthenticationStartScreenViewModelAction {
|
|||||||
struct AuthenticationStartScreenViewState: BindableState {
|
struct AuthenticationStartScreenViewState: BindableState {
|
||||||
let isWebRegistrationEnabled: Bool
|
let isWebRegistrationEnabled: Bool
|
||||||
let isQRCodeLoginEnabled: Bool
|
let isQRCodeLoginEnabled: Bool
|
||||||
|
let isBugReportServiceEnabled: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AuthenticationStartScreenViewAction {
|
enum AuthenticationStartScreenViewAction {
|
||||||
|
|||||||
+3
-2
@@ -17,9 +17,10 @@ class AuthenticationStartScreenViewModel: AuthenticationStartScreenViewModelType
|
|||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(webRegistrationEnabled: Bool) {
|
init(webRegistrationEnabled: Bool, isBugReportServiceEnabled: Bool) {
|
||||||
super.init(initialViewState: AuthenticationStartScreenViewState(isWebRegistrationEnabled: webRegistrationEnabled,
|
super.init(initialViewState: AuthenticationStartScreenViewState(isWebRegistrationEnabled: webRegistrationEnabled,
|
||||||
isQRCodeLoginEnabled: !ProcessInfo.processInfo.isiOSAppOnMac))
|
isQRCodeLoginEnabled: !ProcessInfo.processInfo.isiOSAppOnMac,
|
||||||
|
isBugReportServiceEnabled: isBugReportServiceEnabled))
|
||||||
}
|
}
|
||||||
|
|
||||||
override func process(viewAction: AuthenticationStartScreenViewAction) {
|
override func process(viewAction: AuthenticationStartScreenViewAction) {
|
||||||
|
|||||||
+12
-1
@@ -34,6 +34,7 @@ struct AuthenticationStartScreen: View {
|
|||||||
}
|
}
|
||||||
.frame(maxHeight: .infinity)
|
.frame(maxHeight: .infinity)
|
||||||
.safeAreaInset(edge: .bottom) {
|
.safeAreaInset(edge: .bottom) {
|
||||||
|
if context.viewState.isBugReportServiceEnabled {
|
||||||
Button {
|
Button {
|
||||||
context.send(viewAction: .reportProblem)
|
context.send(viewAction: .reportProblem)
|
||||||
} label: {
|
} label: {
|
||||||
@@ -45,6 +46,7 @@ struct AuthenticationStartScreen: View {
|
|||||||
.frame(width: geometry.size.width)
|
.frame(width: geometry.size.width)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.navigationBarHidden(true)
|
.navigationBarHidden(true)
|
||||||
.background {
|
.background {
|
||||||
AuthenticationStartScreenBackgroundImage()
|
AuthenticationStartScreenBackgroundImage()
|
||||||
@@ -116,9 +118,18 @@ struct AuthenticationStartScreen: View {
|
|||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
||||||
struct AuthenticationStartScreen_Previews: PreviewProvider, TestablePreview {
|
struct AuthenticationStartScreen_Previews: PreviewProvider, TestablePreview {
|
||||||
static let viewModel = AuthenticationStartScreenViewModel(webRegistrationEnabled: true)
|
static let viewModel = makeViewModel()
|
||||||
|
static let bugReportDisabledViewModel = makeViewModel(isBugReportServiceEnabled: false)
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
AuthenticationStartScreen(context: viewModel.context)
|
AuthenticationStartScreen(context: viewModel.context)
|
||||||
|
.previewDisplayName("Default")
|
||||||
|
AuthenticationStartScreen(context: bugReportDisabledViewModel.context)
|
||||||
|
.previewDisplayName("Bug report disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
static func makeViewModel(isBugReportServiceEnabled: Bool = true) -> AuthenticationStartScreenViewModel {
|
||||||
|
AuthenticationStartScreenViewModel(webRegistrationEnabled: true,
|
||||||
|
isBugReportServiceEnabled: isBugReportServiceEnabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ struct BugReportScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
let clientProxy = ClientProxyMock(.init(userID: "@mock:client.com", roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))))
|
let clientProxy = ClientProxyMock(.init(userID: "@mock:client.com", roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))))
|
||||||
BugReportScreen(context: BugReportScreenViewModel(bugReportService: BugReportServiceMock(),
|
BugReportScreen(context: BugReportScreenViewModel(bugReportService: BugReportServiceMock(.init()),
|
||||||
clientProxy: clientProxy,
|
clientProxy: clientProxy,
|
||||||
screenshot: nil,
|
screenshot: nil,
|
||||||
isModallyPresented: false).context)
|
isModallyPresented: false).context)
|
||||||
@@ -150,7 +150,7 @@ struct BugReportScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
|
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
let clientProxy = ClientProxyMock(.init(userID: "@mock:client.com", roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))))
|
let clientProxy = ClientProxyMock(.init(userID: "@mock:client.com", roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))))
|
||||||
BugReportScreen(context: BugReportScreenViewModel(bugReportService: BugReportServiceMock(),
|
BugReportScreen(context: BugReportScreenViewModel(bugReportService: BugReportServiceMock(.init()),
|
||||||
clientProxy: clientProxy,
|
clientProxy: clientProxy,
|
||||||
screenshot: Asset.Images.appLogo.image,
|
screenshot: Asset.Images.appLogo.image,
|
||||||
isModallyPresented: false).context)
|
isModallyPresented: false).context)
|
||||||
|
|||||||
@@ -87,7 +87,9 @@ final class HomeScreenCoordinator: CoordinatorProtocol {
|
|||||||
|
|
||||||
func start() {
|
func start() {
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
if bugReportService.crashedLastRun {
|
// Note: bugReportService.isEnabled doesn't determine if a user has opted in to Analytics/Sentry.
|
||||||
|
// Therefore we use lastCrashEventID as this will only be set if we have crash ID from Sentry.
|
||||||
|
if bugReportService.crashedLastRun, bugReportService.lastCrashEventID != nil {
|
||||||
viewModel.presentCrashedLastRunAlert()
|
viewModel.presentCrashedLastRunAlert()
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+1
-1
@@ -22,7 +22,7 @@ final class AnalyticsPromptScreenCoordinator: CoordinatorProtocol {
|
|||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(analytics: AnalyticsService, termsURL: URL) {
|
init(analytics: AnalyticsService, termsURL: URL?) {
|
||||||
self.analytics = analytics
|
self.analytics = analytics
|
||||||
viewModel = AnalyticsPromptScreenViewModel(termsURL: termsURL)
|
viewModel = AnalyticsPromptScreenViewModel(termsURL: termsURL)
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -33,9 +33,10 @@ struct AnalyticsPromptScreenStrings {
|
|||||||
let point2 = L10n.screenAnalyticsPromptThirdPartySharing
|
let point2 = L10n.screenAnalyticsPromptThirdPartySharing
|
||||||
let point3 = L10n.screenAnalyticsPromptSettings
|
let point3 = L10n.screenAnalyticsPromptSettings
|
||||||
|
|
||||||
init(termsURL: URL) {
|
init(termsURL: URL?) {
|
||||||
let content = AttributedString(L10n.screenAnalyticsPromptHelpUsImprove)
|
let content = AttributedString(L10n.screenAnalyticsPromptHelpUsImprove)
|
||||||
|
|
||||||
|
if let termsURL {
|
||||||
// Create the 'read terms' with a placeholder.
|
// Create the 'read terms' with a placeholder.
|
||||||
let linkPlaceholder = "{link}"
|
let linkPlaceholder = "{link}"
|
||||||
var readTerms = AttributedString(L10n.screenAnalyticsSettingsReadTerms(linkPlaceholder))
|
var readTerms = AttributedString(L10n.screenAnalyticsSettingsReadTerms(linkPlaceholder))
|
||||||
@@ -45,5 +46,8 @@ struct AnalyticsPromptScreenStrings {
|
|||||||
readTerms.replace(linkPlaceholder, with: linkString)
|
readTerms.replace(linkPlaceholder, with: linkString)
|
||||||
|
|
||||||
optInContent = content + "\n\n" + readTerms
|
optInContent = content + "\n\n" + readTerms
|
||||||
|
} else {
|
||||||
|
optInContent = content
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ class AnalyticsPromptScreenViewModel: AnalyticsPromptScreenViewModelType, Analyt
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize a view model with the specified prompt type and app display name.
|
/// Initialize a view model with the specified prompt type and app display name.
|
||||||
init(termsURL: URL) {
|
init(termsURL: URL?) {
|
||||||
let promptStrings = AnalyticsPromptScreenStrings(termsURL: termsURL)
|
let promptStrings = AnalyticsPromptScreenStrings(termsURL: termsURL)
|
||||||
super.init(initialViewState: AnalyticsPromptScreenViewState(strings: promptStrings))
|
super.init(initialViewState: AnalyticsPromptScreenViewState(strings: promptStrings))
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-1
@@ -91,8 +91,17 @@ struct AnalyticsPromptScreen: View {
|
|||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
||||||
struct AnalyticsPromptScreen_Previews: PreviewProvider, TestablePreview {
|
struct AnalyticsPromptScreen_Previews: PreviewProvider, TestablePreview {
|
||||||
static let viewModel = AnalyticsPromptScreenViewModel(termsURL: ServiceLocator.shared.settings.analyticsConfiguration.termsURL)
|
static let viewModel = makeViewModel()
|
||||||
|
static let noTermsViewModel = makeViewModel(showTerms: false)
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
AnalyticsPromptScreen(context: viewModel.context)
|
AnalyticsPromptScreen(context: viewModel.context)
|
||||||
|
.previewDisplayName("Default")
|
||||||
|
AnalyticsPromptScreen(context: noTermsViewModel.context)
|
||||||
|
.previewDisplayName("No terms")
|
||||||
|
}
|
||||||
|
|
||||||
|
static func makeViewModel(showTerms: Bool = true) -> AnalyticsPromptScreenViewModel {
|
||||||
|
AnalyticsPromptScreenViewModel(termsURL: showTerms ? ServiceLocator.shared.settings.analyticsTermsURL : nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ struct ComposerToolbarViewState: BindableState {
|
|||||||
var audioRecorderState: AudioRecorderState
|
var audioRecorderState: AudioRecorderState
|
||||||
|
|
||||||
var isRoomEncrypted: Bool
|
var isRoomEncrypted: Bool
|
||||||
|
var isLocationSharingEnabled: Bool
|
||||||
|
|
||||||
var bindings: ComposerToolbarViewStateBindings
|
var bindings: ComposerToolbarViewStateBindings
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
|
|||||||
completionSuggestionService: CompletionSuggestionServiceProtocol,
|
completionSuggestionService: CompletionSuggestionServiceProtocol,
|
||||||
mediaProvider: MediaProviderProtocol,
|
mediaProvider: MediaProviderProtocol,
|
||||||
mentionDisplayHelper: MentionDisplayHelper,
|
mentionDisplayHelper: MentionDisplayHelper,
|
||||||
|
appSettings: AppSettings,
|
||||||
analyticsService: AnalyticsService,
|
analyticsService: AnalyticsService,
|
||||||
composerDraftService: ComposerDraftServiceProtocol) {
|
composerDraftService: ComposerDraftServiceProtocol) {
|
||||||
self.initialText = initialText
|
self.initialText = initialText
|
||||||
@@ -65,6 +66,7 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
|
|||||||
super.init(initialViewState: ComposerToolbarViewState(audioPlayerState: .init(id: .recorderPreview, title: L10n.commonVoiceMessage, duration: 0),
|
super.init(initialViewState: ComposerToolbarViewState(audioPlayerState: .init(id: .recorderPreview, title: L10n.commonVoiceMessage, duration: 0),
|
||||||
audioRecorderState: .init(),
|
audioRecorderState: .init(),
|
||||||
isRoomEncrypted: roomProxy.infoPublisher.value.isEncrypted,
|
isRoomEncrypted: roomProxy.infoPublisher.value.isEncrypted,
|
||||||
|
isLocationSharingEnabled: appSettings.mapTilerConfiguration.isEnabled,
|
||||||
bindings: .init()),
|
bindings: .init()),
|
||||||
mediaProvider: mediaProvider)
|
mediaProvider: mediaProvider)
|
||||||
|
|
||||||
|
|||||||
@@ -319,6 +319,7 @@ struct ComposerToolbar_Previews: PreviewProvider, TestablePreview {
|
|||||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init(suggestions: suggestions)),
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init(suggestions: suggestions)),
|
||||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
||||||
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
composerDraftService: ComposerDraftServiceMock())
|
composerDraftService: ComposerDraftServiceMock())
|
||||||
|
|
||||||
@@ -369,6 +370,7 @@ extension ComposerToolbar {
|
|||||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
||||||
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
composerDraftService: ComposerDraftServiceMock())
|
composerDraftService: ComposerDraftServiceMock())
|
||||||
model.state.composerEmpty = focused
|
model.state.composerEmpty = focused
|
||||||
@@ -387,6 +389,7 @@ extension ComposerToolbar {
|
|||||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
||||||
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
composerDraftService: ComposerDraftServiceMock())
|
composerDraftService: ComposerDraftServiceMock())
|
||||||
model.state.composerEmpty = focused
|
model.state.composerEmpty = focused
|
||||||
@@ -405,6 +408,7 @@ extension ComposerToolbar {
|
|||||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
||||||
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
composerDraftService: ComposerDraftServiceMock())
|
composerDraftService: ComposerDraftServiceMock())
|
||||||
model.state.composerMode = .recordVoiceMessage(state: AudioRecorderState())
|
model.state.composerMode = .recordVoiceMessage(state: AudioRecorderState())
|
||||||
@@ -424,6 +428,7 @@ extension ComposerToolbar {
|
|||||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
||||||
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
composerDraftService: ComposerDraftServiceMock())
|
composerDraftService: ComposerDraftServiceMock())
|
||||||
model.state.composerMode = .previewVoiceMessage(state: AudioPlayerState(id: .recorderPreview,
|
model.state.composerMode = .previewVoiceMessage(state: AudioPlayerState(id: .recorderPreview,
|
||||||
@@ -446,6 +451,7 @@ extension ComposerToolbar {
|
|||||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
||||||
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
composerDraftService: ComposerDraftServiceMock())
|
composerDraftService: ComposerDraftServiceMock())
|
||||||
model.state.composerMode = isLoading ? .reply(eventID: UUID().uuidString,
|
model.state.composerMode = isLoading ? .reply(eventID: UUID().uuidString,
|
||||||
@@ -470,6 +476,7 @@ extension ComposerToolbar {
|
|||||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
||||||
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
composerDraftService: ComposerDraftServiceMock())
|
composerDraftService: ComposerDraftServiceMock())
|
||||||
model.state.canSend = false
|
model.state.canSend = false
|
||||||
|
|||||||
@@ -47,12 +47,14 @@ struct RoomAttachmentPicker: View {
|
|||||||
}
|
}
|
||||||
.accessibilityIdentifier(A11yIdentifiers.roomScreen.attachmentPickerPoll)
|
.accessibilityIdentifier(A11yIdentifiers.roomScreen.attachmentPickerPoll)
|
||||||
|
|
||||||
|
if context.viewState.isLocationSharingEnabled {
|
||||||
Button {
|
Button {
|
||||||
context.send(viewAction: .attach(.location))
|
context.send(viewAction: .attach(.location))
|
||||||
} label: {
|
} label: {
|
||||||
Label(L10n.screenRoomAttachmentSourceLocation, icon: \.locationPin)
|
Label(L10n.screenRoomAttachmentSourceLocation, icon: \.locationPin)
|
||||||
}
|
}
|
||||||
.accessibilityIdentifier(A11yIdentifiers.roomScreen.attachmentPickerLocation)
|
.accessibilityIdentifier(A11yIdentifiers.roomScreen.attachmentPickerLocation)
|
||||||
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
context.send(viewAction: .attach(.file))
|
context.send(viewAction: .attach(.file))
|
||||||
@@ -91,6 +93,7 @@ struct RoomAttachmentPicker_Previews: PreviewProvider, TestablePreview {
|
|||||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
||||||
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
composerDraftService: ComposerDraftServiceMock())
|
composerDraftService: ComposerDraftServiceMock())
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
|||||||
mediaProvider: parameters.mediaProvider,
|
mediaProvider: parameters.mediaProvider,
|
||||||
ongoingCallRoomIDPublisher: parameters.ongoingCallRoomIDPublisher,
|
ongoingCallRoomIDPublisher: parameters.ongoingCallRoomIDPublisher,
|
||||||
appMediator: parameters.appMediator,
|
appMediator: parameters.appMediator,
|
||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: parameters.appSettings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||||
|
|
||||||
@@ -99,6 +99,7 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
|||||||
completionSuggestionService: parameters.completionSuggestionService,
|
completionSuggestionService: parameters.completionSuggestionService,
|
||||||
mediaProvider: parameters.mediaProvider,
|
mediaProvider: parameters.mediaProvider,
|
||||||
mentionDisplayHelper: ComposerMentionDisplayHelper(timelineContext: timelineViewModel.context),
|
mentionDisplayHelper: ComposerMentionDisplayHelper(timelineContext: timelineViewModel.context),
|
||||||
|
appSettings: parameters.appSettings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
composerDraftService: parameters.composerDraftService)
|
composerDraftService: parameters.composerDraftService)
|
||||||
self.composerViewModel = composerViewModel
|
self.composerViewModel = composerViewModel
|
||||||
|
|||||||
+5
-1
@@ -24,9 +24,10 @@ enum AnalyticsSettingsScreenViewAction {
|
|||||||
struct AnalyticsSettingsScreenStrings {
|
struct AnalyticsSettingsScreenStrings {
|
||||||
let sectionFooter: AttributedString
|
let sectionFooter: AttributedString
|
||||||
|
|
||||||
init(termsURL: URL) {
|
init(termsURL: URL?) {
|
||||||
let content = AttributedString(L10n.screenAnalyticsPromptHelpUsImprove)
|
let content = AttributedString(L10n.screenAnalyticsPromptHelpUsImprove)
|
||||||
|
|
||||||
|
if let termsURL {
|
||||||
// Create the 'read terms' with a placeholder.
|
// Create the 'read terms' with a placeholder.
|
||||||
let linkPlaceholder = "{link}"
|
let linkPlaceholder = "{link}"
|
||||||
var readTerms = AttributedString(L10n.screenAnalyticsSettingsReadTerms(linkPlaceholder))
|
var readTerms = AttributedString(L10n.screenAnalyticsSettingsReadTerms(linkPlaceholder))
|
||||||
@@ -36,5 +37,8 @@ struct AnalyticsSettingsScreenStrings {
|
|||||||
readTerms.replace(linkPlaceholder, with: linkString)
|
readTerms.replace(linkPlaceholder, with: linkString)
|
||||||
|
|
||||||
sectionFooter = content + "\n\n" + readTerms
|
sectionFooter = content + "\n\n" + readTerms
|
||||||
|
} else {
|
||||||
|
sectionFooter = content
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ class AnalyticsSettingsScreenViewModel: AnalyticsSettingsScreenViewModelType, An
|
|||||||
init(appSettings: AppSettings, analytics: AnalyticsService) {
|
init(appSettings: AppSettings, analytics: AnalyticsService) {
|
||||||
self.analytics = analytics
|
self.analytics = analytics
|
||||||
|
|
||||||
let strings = AnalyticsSettingsScreenStrings(termsURL: appSettings.analyticsConfiguration.termsURL)
|
let strings = AnalyticsSettingsScreenStrings(termsURL: appSettings.analyticsTermsURL)
|
||||||
let bindings = AnalyticsSettingsScreenViewStateBindings(enableAnalytics: analytics.isEnabled)
|
let bindings = AnalyticsSettingsScreenViewStateBindings(enableAnalytics: analytics.isEnabled)
|
||||||
let state = AnalyticsSettingsScreenViewState(strings: strings, bindings: bindings)
|
let state = AnalyticsSettingsScreenViewState(strings: strings, bindings: bindings)
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import SwiftUI
|
|||||||
struct SettingsScreenCoordinatorParameters {
|
struct SettingsScreenCoordinatorParameters {
|
||||||
let userSession: UserSessionProtocol
|
let userSession: UserSessionProtocol
|
||||||
let appSettings: AppSettings
|
let appSettings: AppSettings
|
||||||
|
let isBugReportServiceEnabled: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SettingsScreenCoordinatorAction {
|
enum SettingsScreenCoordinatorAction {
|
||||||
@@ -43,7 +44,9 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
|
|||||||
// MARK: - Setup
|
// MARK: - Setup
|
||||||
|
|
||||||
init(parameters: SettingsScreenCoordinatorParameters) {
|
init(parameters: SettingsScreenCoordinatorParameters) {
|
||||||
viewModel = SettingsScreenViewModel(userSession: parameters.userSession)
|
viewModel = SettingsScreenViewModel(userSession: parameters.userSession,
|
||||||
|
appSettings: parameters.appSettings,
|
||||||
|
isBugReportServiceEnabled: parameters.isBugReportServiceEnabled)
|
||||||
|
|
||||||
viewModel.actions
|
viewModel.actions
|
||||||
.sink { [weak self] action in
|
.sink { [weak self] action in
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ struct SettingsScreenViewState: BindableState {
|
|||||||
var showSecuritySectionBadge = false
|
var showSecuritySectionBadge = false
|
||||||
|
|
||||||
var showBlockedUsers = false
|
var showBlockedUsers = false
|
||||||
|
let showAnalyticsSettings: Bool
|
||||||
|
|
||||||
|
let isBugReportServiceEnabled: Bool
|
||||||
|
|
||||||
var bindings = SettingsScreenViewStateBindings()
|
var bindings = SettingsScreenViewStateBindings()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,13 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
|
|||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(userSession: UserSessionProtocol) {
|
init(userSession: UserSessionProtocol, appSettings: AppSettings, isBugReportServiceEnabled: Bool) {
|
||||||
super.init(initialViewState: .init(deviceID: userSession.clientProxy.deviceID,
|
super.init(initialViewState: .init(deviceID: userSession.clientProxy.deviceID,
|
||||||
userID: userSession.clientProxy.userID,
|
userID: userSession.clientProxy.userID,
|
||||||
showAccountDeactivation: userSession.clientProxy.canDeactivateAccount,
|
showAccountDeactivation: userSession.clientProxy.canDeactivateAccount,
|
||||||
showDeveloperOptions: AppSettings.isDevelopmentBuild),
|
showDeveloperOptions: AppSettings.isDevelopmentBuild,
|
||||||
|
showAnalyticsSettings: appSettings.canPromptForAnalytics,
|
||||||
|
isBugReportServiceEnabled: isBugReportServiceEnabled),
|
||||||
mediaProvider: userSession.mediaProvider)
|
mediaProvider: userSession.mediaProvider)
|
||||||
|
|
||||||
userSession.clientProxy.userAvatarURLPublisher
|
userSession.clientProxy.userAvatarURLPublisher
|
||||||
|
|||||||
@@ -140,19 +140,23 @@ struct SettingsScreen: View {
|
|||||||
})
|
})
|
||||||
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.about)
|
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.about)
|
||||||
|
|
||||||
|
if context.viewState.isBugReportServiceEnabled {
|
||||||
ListRow(label: .default(title: L10n.commonReportAProblem,
|
ListRow(label: .default(title: L10n.commonReportAProblem,
|
||||||
icon: \.chatProblem),
|
icon: \.chatProblem),
|
||||||
kind: .navigationLink {
|
kind: .navigationLink {
|
||||||
context.send(viewAction: .reportBug)
|
context.send(viewAction: .reportBug)
|
||||||
})
|
})
|
||||||
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.reportBug)
|
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.reportBug)
|
||||||
|
}
|
||||||
|
|
||||||
|
if context.viewState.showAnalyticsSettings {
|
||||||
ListRow(label: .default(title: L10n.commonAnalytics,
|
ListRow(label: .default(title: L10n.commonAnalytics,
|
||||||
icon: \.chart),
|
icon: \.chart),
|
||||||
kind: .navigationLink {
|
kind: .navigationLink {
|
||||||
context.send(viewAction: .analytics)
|
context.send(viewAction: .analytics)
|
||||||
})
|
})
|
||||||
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.analytics)
|
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.analytics)
|
||||||
|
}
|
||||||
|
|
||||||
ListRow(label: .default(title: L10n.commonAdvancedSettings,
|
ListRow(label: .default(title: L10n.commonAdvancedSettings,
|
||||||
icon: \.settings),
|
icon: \.settings),
|
||||||
@@ -230,18 +234,32 @@ struct SettingsScreen: View {
|
|||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
||||||
struct SettingsScreen_Previews: PreviewProvider, TestablePreview {
|
struct SettingsScreen_Previews: PreviewProvider, TestablePreview {
|
||||||
static let viewModel = {
|
static let viewModel = makeViewModel()
|
||||||
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "@userid:example.com",
|
static let bugReportDisabledViewModel = makeViewModel(isBugReportServiceEnabled: false)
|
||||||
deviceID: "AAAAAAAAAAA"))))
|
|
||||||
return SettingsScreenViewModel(userSession: userSession)
|
|
||||||
}()
|
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
SettingsScreen(context: viewModel.context)
|
SettingsScreen(context: viewModel.context)
|
||||||
|
}
|
||||||
.snapshotPreferences(expect: viewModel.context.$viewState.map { state in
|
.snapshotPreferences(expect: viewModel.context.$viewState.map { state in
|
||||||
state.accountSessionsListURL != nil
|
state.accountSessionsListURL != nil
|
||||||
})
|
})
|
||||||
|
.previewDisplayName("Default")
|
||||||
|
|
||||||
|
NavigationStack {
|
||||||
|
SettingsScreen(context: bugReportDisabledViewModel.context)
|
||||||
}
|
}
|
||||||
|
.snapshotPreferences(expect: bugReportDisabledViewModel.context.$viewState.map { state in
|
||||||
|
state.accountSessionsListURL != nil
|
||||||
|
})
|
||||||
|
.previewDisplayName("Bug report disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
static func makeViewModel(isBugReportServiceEnabled: Bool = true) -> SettingsScreenViewModel {
|
||||||
|
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "@userid:example.com",
|
||||||
|
deviceID: "AAAAAAAAAAA"))))
|
||||||
|
return SettingsScreenViewModel(userSession: userSession,
|
||||||
|
appSettings: ServiceLocator.shared.settings,
|
||||||
|
isBugReportServiceEnabled: isBugReportServiceEnabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ struct TimelineViewState: BindableState {
|
|||||||
|
|
||||||
var emojiProvider: EmojiProviderProtocol
|
var emojiProvider: EmojiProviderProtocol
|
||||||
|
|
||||||
var mapURLBuilder: MapTilerURLBuilderProtocol
|
var mapTilerConfiguration: MapTilerConfiguration
|
||||||
|
|
||||||
var bindings: TimelineViewStateBindings
|
var bindings: TimelineViewStateBindings
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
hideTimelineMedia: appSettings.hideTimelineMedia,
|
hideTimelineMedia: appSettings.hideTimelineMedia,
|
||||||
pinnedEventIDs: roomProxy.infoPublisher.value.pinnedEventIDs,
|
pinnedEventIDs: roomProxy.infoPublisher.value.pinnedEventIDs,
|
||||||
emojiProvider: emojiProvider,
|
emojiProvider: emojiProvider,
|
||||||
mapURLBuilder: appSettings.mapTilerConfiguration,
|
mapTilerConfiguration: appSettings.mapTilerConfiguration,
|
||||||
bindings: .init(reactionsCollapsed: [:])),
|
bindings: .init(reactionsCollapsed: [:])),
|
||||||
mediaProvider: mediaProvider)
|
mediaProvider: mediaProvider)
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -17,6 +17,7 @@ struct LocationRoomTimelineView: View {
|
|||||||
.accessibilityElement(children: .ignore)
|
.accessibilityElement(children: .ignore)
|
||||||
.accessibilityLabel(accessibilityLabel)
|
.accessibilityLabel(accessibilityLabel)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
|
guard context.viewState.mapTilerConfiguration.isEnabled else { return }
|
||||||
context.send(viewAction: .mediaTapped(itemID: timelineItem.id))
|
context.send(viewAction: .mediaTapped(itemID: timelineItem.id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,7 +31,7 @@ struct LocationRoomTimelineView: View {
|
|||||||
.frame(maxWidth: mapAspectRatio * mapMaxHeight, alignment: .leading)
|
.frame(maxWidth: mapAspectRatio * mapMaxHeight, alignment: .leading)
|
||||||
|
|
||||||
MapLibreStaticMapView(geoURI: geoURI,
|
MapLibreStaticMapView(geoURI: geoURI,
|
||||||
mapURLBuilder: context.viewState.mapURLBuilder,
|
mapURLBuilder: context.viewState.mapTilerConfiguration,
|
||||||
mapSize: .init(width: mapAspectRatio * mapMaxHeight, height: mapMaxHeight)) {
|
mapSize: .init(width: mapAspectRatio * mapMaxHeight, height: mapMaxHeight)) {
|
||||||
LocationMarkerView()
|
LocationMarkerView()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,8 @@ import Foundation
|
|||||||
|
|
||||||
/// A type that represents how to set up the analytics module in the app.
|
/// A type that represents how to set up the analytics module in the app.
|
||||||
struct AnalyticsConfiguration {
|
struct AnalyticsConfiguration {
|
||||||
/// Whether or not analytics should be enabled.
|
|
||||||
let isEnabled: Bool
|
|
||||||
/// The host to use for PostHog analytics.
|
/// The host to use for PostHog analytics.
|
||||||
let host: String
|
let host: String
|
||||||
/// The public key for submitting analytics.
|
/// The public key for submitting analytics.
|
||||||
let apiKey: String
|
let apiKey: String
|
||||||
/// The URL to open with more information about analytics terms.
|
|
||||||
let termsURL: URL
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class AnalyticsService {
|
|||||||
/// Whether to show the user the analytics opt in prompt.
|
/// Whether to show the user the analytics opt in prompt.
|
||||||
var shouldShowAnalyticsPrompt: Bool {
|
var shouldShowAnalyticsPrompt: Bool {
|
||||||
// Only show the prompt once, and when analytics are enabled in BuildSettings.
|
// Only show the prompt once, and when analytics are enabled in BuildSettings.
|
||||||
appSettings.analyticsConsentState == .unknown && appSettings.analyticsConfiguration.isEnabled
|
appSettings.analyticsConsentState == .unknown && appSettings.canPromptForAnalytics
|
||||||
}
|
}
|
||||||
|
|
||||||
var isEnabled: Bool {
|
var isEnabled: Bool {
|
||||||
@@ -65,9 +65,9 @@ class AnalyticsService {
|
|||||||
|
|
||||||
/// Starts the analytics client if the user has opted in, otherwise does nothing.
|
/// Starts the analytics client if the user has opted in, otherwise does nothing.
|
||||||
func startIfEnabled() {
|
func startIfEnabled() {
|
||||||
guard isEnabled, !client.isRunning else { return }
|
guard isEnabled, !client.isRunning, let configuration = appSettings.analyticsConfiguration else { return }
|
||||||
|
|
||||||
client.start(analyticsConfiguration: appSettings.analyticsConfiguration)
|
client.start(analyticsConfiguration: configuration)
|
||||||
|
|
||||||
// Sanity check in case something went wrong.
|
// Sanity check in case something went wrong.
|
||||||
guard client.isRunning else { return }
|
guard client.isRunning else { return }
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ import PostHog
|
|||||||
|
|
||||||
extension PostHogConfig {
|
extension PostHogConfig {
|
||||||
static func standard(analyticsConfiguration: AnalyticsConfiguration) -> PostHogConfig? {
|
static func standard(analyticsConfiguration: AnalyticsConfiguration) -> PostHogConfig? {
|
||||||
guard analyticsConfiguration.isEnabled else { return nil }
|
|
||||||
|
|
||||||
let postHogConfiguration = PostHogConfig(apiKey: analyticsConfiguration.apiKey, host: analyticsConfiguration.host)
|
let postHogConfiguration = PostHogConfig(apiKey: analyticsConfiguration.apiKey, host: analyticsConfiguration.host)
|
||||||
// We capture screens manually
|
// We capture screens manually
|
||||||
postHogConfiguration.captureScreenViews = false
|
postHogConfiguration.captureScreenViews = false
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import Sentry
|
|||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class BugReportService: NSObject, BugReportServiceProtocol {
|
class BugReportService: NSObject, BugReportServiceProtocol {
|
||||||
private let baseURL: URL
|
private let baseURL: URL?
|
||||||
private let applicationId: String
|
private let applicationID: String
|
||||||
private let sdkGitSHA: String
|
private let sdkGitSHA: String
|
||||||
private let maxUploadSize: Int
|
private let maxUploadSize: Int
|
||||||
private let session: URLSession
|
private let session: URLSession
|
||||||
@@ -23,16 +23,17 @@ class BugReportService: NSObject, BugReportServiceProtocol {
|
|||||||
private let progressSubject = PassthroughSubject<Double, Never>()
|
private let progressSubject = PassthroughSubject<Double, Never>()
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
var isEnabled: Bool { baseURL != nil }
|
||||||
var lastCrashEventID: String?
|
var lastCrashEventID: String?
|
||||||
|
|
||||||
init(withBaseURL baseURL: URL,
|
init(baseURL: URL?,
|
||||||
applicationId: String,
|
applicationID: String,
|
||||||
sdkGitSHA: String,
|
sdkGitSHA: String,
|
||||||
maxUploadSize: Int,
|
maxUploadSize: Int,
|
||||||
session: URLSession = .shared,
|
session: URLSession = .shared,
|
||||||
appHooks: AppHooks) {
|
appHooks: AppHooks) {
|
||||||
self.baseURL = baseURL
|
self.baseURL = baseURL
|
||||||
self.applicationId = applicationId
|
self.applicationID = applicationID
|
||||||
self.sdkGitSHA = sdkGitSHA
|
self.sdkGitSHA = sdkGitSHA
|
||||||
self.maxUploadSize = maxUploadSize
|
self.maxUploadSize = maxUploadSize
|
||||||
self.session = session
|
self.session = session
|
||||||
@@ -49,6 +50,10 @@ class BugReportService: NSObject, BugReportServiceProtocol {
|
|||||||
// swiftlint:disable:next cyclomatic_complexity
|
// swiftlint:disable:next cyclomatic_complexity
|
||||||
func submitBugReport(_ bugReport: BugReport,
|
func submitBugReport(_ bugReport: BugReport,
|
||||||
progressListener: CurrentValueSubject<Double, Never>) async -> Result<SubmitBugReportResponse, BugReportServiceError> {
|
progressListener: CurrentValueSubject<Double, Never>) async -> Result<SubmitBugReportResponse, BugReportServiceError> {
|
||||||
|
guard let baseURL else {
|
||||||
|
fatalError("No bug report URL set, the screen should not be shown in this case.")
|
||||||
|
}
|
||||||
|
|
||||||
let bugReport = appHooks.bugReportHook.update(bugReport)
|
let bugReport = appHooks.bugReportHook.update(bugReport)
|
||||||
|
|
||||||
var params = [
|
var params = [
|
||||||
@@ -153,7 +158,7 @@ class BugReportService: NSObject, BugReportServiceProtocol {
|
|||||||
let (localTime, utcTime) = localAndUTCTime(for: Date())
|
let (localTime, utcTime) = localAndUTCTime(for: Date())
|
||||||
return [
|
return [
|
||||||
MultipartFormData(key: "user_agent", type: .text(value: "iOS")),
|
MultipartFormData(key: "user_agent", type: .text(value: "iOS")),
|
||||||
MultipartFormData(key: "app", type: .text(value: applicationId)),
|
MultipartFormData(key: "app", type: .text(value: applicationID)),
|
||||||
MultipartFormData(key: "version", type: .text(value: InfoPlistReader.main.bundleShortVersionString)),
|
MultipartFormData(key: "version", type: .text(value: InfoPlistReader.main.bundleShortVersionString)),
|
||||||
MultipartFormData(key: "build", type: .text(value: InfoPlistReader.main.bundleVersion)),
|
MultipartFormData(key: "build", type: .text(value: InfoPlistReader.main.bundleVersion)),
|
||||||
MultipartFormData(key: "sdk_sha", type: .text(value: sdkGitSHA)),
|
MultipartFormData(key: "sdk_sha", type: .text(value: sdkGitSHA)),
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ enum BugReportServiceError: LocalizedError {
|
|||||||
|
|
||||||
// sourcery: AutoMockable
|
// sourcery: AutoMockable
|
||||||
protocol BugReportServiceProtocol: AnyObject {
|
protocol BugReportServiceProtocol: AnyObject {
|
||||||
|
var isEnabled: Bool { get }
|
||||||
var crashedLastRun: Bool { get }
|
var crashedLastRun: Bool { get }
|
||||||
|
|
||||||
var lastCrashEventID: String? { get set }
|
var lastCrashEventID: String? { get set }
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class UITestsAppCoordinator: AppCoordinatorProtocol, SecureWindowManagerDelegate
|
|||||||
AppSettings.configureWithSuiteName("io.element.elementx.uitests")
|
AppSettings.configureWithSuiteName("io.element.elementx.uitests")
|
||||||
AppSettings.resetAllSettings()
|
AppSettings.resetAllSettings()
|
||||||
ServiceLocator.shared.register(appSettings: AppSettings())
|
ServiceLocator.shared.register(appSettings: AppSettings())
|
||||||
ServiceLocator.shared.register(bugReportService: BugReportServiceMock())
|
ServiceLocator.shared.register(bugReportService: BugReportServiceMock(.init()))
|
||||||
|
|
||||||
let analyticsClient = AnalyticsClientMock()
|
let analyticsClient = AnalyticsClientMock()
|
||||||
analyticsClient.isRunning = false
|
analyticsClient.isRunning = false
|
||||||
@@ -124,7 +124,7 @@ class MockScreen: Identifiable {
|
|||||||
case .authenticationFlow:
|
case .authenticationFlow:
|
||||||
let flowCoordinator = AuthenticationFlowCoordinator(authenticationService: AuthenticationService.mock,
|
let flowCoordinator = AuthenticationFlowCoordinator(authenticationService: AuthenticationService.mock,
|
||||||
qrCodeLoginService: QRCodeLoginServiceMock(),
|
qrCodeLoginService: QRCodeLoginServiceMock(),
|
||||||
bugReportService: BugReportServiceMock(),
|
bugReportService: BugReportServiceMock(.init()),
|
||||||
navigationRootCoordinator: navigationRootCoordinator,
|
navigationRootCoordinator: navigationRootCoordinator,
|
||||||
appMediator: AppMediatorMock.default,
|
appMediator: AppMediatorMock.default,
|
||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
@@ -228,7 +228,7 @@ class MockScreen: Identifiable {
|
|||||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||||
let clientProxy = ClientProxyMock(.init(userID: "@mock:client.com", roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))))
|
let clientProxy = ClientProxyMock(.init(userID: "@mock:client.com", roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))))
|
||||||
let userSession = UserSessionMock(.init(clientProxy: clientProxy))
|
let userSession = UserSessionMock(.init(clientProxy: clientProxy))
|
||||||
let coordinator = BugReportScreenCoordinator(parameters: .init(bugReportService: BugReportServiceMock(),
|
let coordinator = BugReportScreenCoordinator(parameters: .init(bugReportService: BugReportServiceMock(.init()),
|
||||||
userSession: userSession,
|
userSession: userSession,
|
||||||
userIndicatorController: nil,
|
userIndicatorController: nil,
|
||||||
screenshot: nil,
|
screenshot: nil,
|
||||||
@@ -557,7 +557,7 @@ class MockScreen: Identifiable {
|
|||||||
navigationRootCoordinator: navigationRootCoordinator,
|
navigationRootCoordinator: navigationRootCoordinator,
|
||||||
appLockService: AppLockService(keychainController: KeychainControllerMock(),
|
appLockService: AppLockService(keychainController: KeychainControllerMock(),
|
||||||
appSettings: ServiceLocator.shared.settings),
|
appSettings: ServiceLocator.shared.settings),
|
||||||
bugReportService: BugReportServiceMock(),
|
bugReportService: BugReportServiceMock(.init()),
|
||||||
elementCallService: ElementCallServiceMock(.init()),
|
elementCallService: ElementCallServiceMock(.init()),
|
||||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||||
appMediator: appMediator,
|
appMediator: appMediator,
|
||||||
@@ -711,7 +711,7 @@ class MockScreen: Identifiable {
|
|||||||
navigationRootCoordinator: navigationRootCoordinator,
|
navigationRootCoordinator: navigationRootCoordinator,
|
||||||
appLockService: AppLockService(keychainController: KeychainControllerMock(),
|
appLockService: AppLockService(keychainController: KeychainControllerMock(),
|
||||||
appSettings: ServiceLocator.shared.settings),
|
appSettings: ServiceLocator.shared.settings),
|
||||||
bugReportService: BugReportServiceMock(),
|
bugReportService: BugReportServiceMock(.init()),
|
||||||
elementCallService: ElementCallServiceMock(.init()),
|
elementCallService: ElementCallServiceMock(.init()),
|
||||||
timelineControllerFactory: TimelineControllerFactoryMock(.init(timelineController: timelineController)),
|
timelineControllerFactory: TimelineControllerFactoryMock(.init(timelineController: timelineController)),
|
||||||
appMediator: AppMediatorMock.default,
|
appMediator: AppMediatorMock.default,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class UnitTestsAppCoordinator: AppCoordinatorProtocol {
|
|||||||
AppSettings.configureWithSuiteName("io.element.elementx.unittests")
|
AppSettings.configureWithSuiteName("io.element.elementx.unittests")
|
||||||
AppSettings.resetAllSettings()
|
AppSettings.resetAllSettings()
|
||||||
ServiceLocator.shared.register(appSettings: AppSettings())
|
ServiceLocator.shared.register(appSettings: AppSettings())
|
||||||
ServiceLocator.shared.register(bugReportService: BugReportServiceMock())
|
ServiceLocator.shared.register(bugReportService: BugReportServiceMock(.init()))
|
||||||
|
|
||||||
let analyticsClient = AnalyticsClientMock()
|
let analyticsClient = AnalyticsClientMock()
|
||||||
analyticsClient.isRunning = false
|
analyticsClient.isRunning = false
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:b6fcab1ccc72812d59a6b927c7731b7c1cae82b457a86ed6530cf610e9ae55f1
|
||||||
|
size 509252
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:725a62a99cc8483ecb291544dd58028b9e967e8ca3c63a6b64a8cfc1def27800
|
||||||
|
size 529773
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:ae449487cd4e9b77c360a17f57e117a7ec3048a88f33d53f72fbcc22e6c666e4
|
||||||
|
size 316319
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:c87868f81842fdeeac6f51a73334b1cc1f9c48004256f9e9a50dbaa507c5092b
|
||||||
|
size 332515
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:816f085c82fc0866cc1052e8b16bc6cb035d5f69cdc8388dd44d8a5ced97b34e
|
||||||
|
size 109090
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:ba61a003238a0fb7b3188dca3f5735c386c9d2bd6488ae15d31376d3b1899a73
|
||||||
|
size 126888
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:d7b8b73c30044017dc8db3f5b540011fb9e502d68beabfae0f85daede922f342
|
||||||
|
size 521551
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:3074c78aa668a0b9f194e85bbd41af06985c831e6680f4d7bc8c131f0acdb185
|
||||||
|
size 544472
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:82aceb5956ac84373d141855acaa16a962034001d2b4a01c4bd30a347505e2f8
|
||||||
|
size 160152
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:0ce61dcd6449607c67a4aa12d96140e1159afce33481351a16e7af9f90c5e932
|
||||||
|
size 166763
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:967c85e91f106b89c44f17facb9ac369bbca2e19e33b1cd6875108aba2fd5b49
|
||||||
|
size 106646
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:eab5bec1eb422c8918f0e7c634d2b1cd3b1190dc001547db99159395241fea3e
|
||||||
|
size 123586
|
||||||
@@ -155,11 +155,11 @@ class AnalyticsTests: XCTestCase {
|
|||||||
XCTAssertEqual(client.pendingUserProperties?.numSpaces, 5, "The number of spaces should have been updated.")
|
XCTAssertEqual(client.pendingUserProperties?.numSpaces, 5, "The number of spaces should have been updated.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSendingUserProperties() {
|
func testSendingUserProperties() throws {
|
||||||
// Given a client with user properties set
|
// Given a client with user properties set
|
||||||
|
|
||||||
let client = PostHogAnalyticsClient(posthogFactory: MockPostHogFactory(mock: posthogMock))
|
let client = PostHogAnalyticsClient(posthogFactory: MockPostHogFactory(mock: posthogMock))
|
||||||
client.start(analyticsConfiguration: appSettings.analyticsConfiguration)
|
try client.start(analyticsConfiguration: XCTUnwrap(appSettings.analyticsConfiguration))
|
||||||
|
|
||||||
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging,
|
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging,
|
||||||
numFavouriteRooms: nil,
|
numFavouriteRooms: nil,
|
||||||
@@ -204,10 +204,10 @@ class AnalyticsTests: XCTestCase {
|
|||||||
XCTAssertTrue(ServiceLocator.shared.analytics.shouldShowAnalyticsPrompt)
|
XCTAssertTrue(ServiceLocator.shared.analytics.shouldShowAnalyticsPrompt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSendingAndUpdatingSuperProperties() {
|
func testSendingAndUpdatingSuperProperties() throws {
|
||||||
// Given a client with user properties set
|
// Given a client with user properties set
|
||||||
let client = PostHogAnalyticsClient(posthogFactory: MockPostHogFactory(mock: posthogMock))
|
let client = PostHogAnalyticsClient(posthogFactory: MockPostHogFactory(mock: posthogMock))
|
||||||
client.start(analyticsConfiguration: appSettings.analyticsConfiguration)
|
try client.start(analyticsConfiguration: XCTUnwrap(appSettings.analyticsConfiguration))
|
||||||
|
|
||||||
client.updateSuperProperties(
|
client.updateSuperProperties(
|
||||||
AnalyticsEvent.SuperProperties(appPlatform: .EXI,
|
AnalyticsEvent.SuperProperties(appPlatform: .EXI,
|
||||||
@@ -264,7 +264,7 @@ class AnalyticsTests: XCTestCase {
|
|||||||
XCTAssertEqual(capturedEvent2?.properties?["cryptoSDKVersion"] as? String, "001")
|
XCTAssertEqual(capturedEvent2?.properties?["cryptoSDKVersion"] as? String, "001")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testShouldNotReportIfNotStarted() {
|
func testShouldNotReportIfNotStarted() throws {
|
||||||
// Given a client with user properties set
|
// Given a client with user properties set
|
||||||
let client = PostHogAnalyticsClient(posthogFactory: MockPostHogFactory(mock: posthogMock))
|
let client = PostHogAnalyticsClient(posthogFactory: MockPostHogFactory(mock: posthogMock))
|
||||||
|
|
||||||
@@ -291,7 +291,7 @@ class AnalyticsTests: XCTestCase {
|
|||||||
XCTAssertEqual(posthogMock.capturePropertiesUserPropertiesCalled, false)
|
XCTAssertEqual(posthogMock.capturePropertiesUserPropertiesCalled, false)
|
||||||
|
|
||||||
// start now
|
// start now
|
||||||
client.start(analyticsConfiguration: appSettings.analyticsConfiguration)
|
try client.start(analyticsConfiguration: XCTUnwrap(appSettings.analyticsConfiguration))
|
||||||
XCTAssertEqual(posthogMock.optInCalled, true)
|
XCTAssertEqual(posthogMock.optInCalled, true)
|
||||||
|
|
||||||
client.capture(someEvent)
|
client.capture(someEvent)
|
||||||
|
|||||||
@@ -41,18 +41,30 @@ class BugReportServiceTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testInitialStateWithRealService() throws {
|
func testInitialStateWithRealService() throws {
|
||||||
let service = BugReportService(withBaseURL: "https://www.example.com",
|
let service = BugReportService(baseURL: "https://www.example.com",
|
||||||
applicationId: "mock_app_id",
|
applicationID: "mock_app_id",
|
||||||
sdkGitSHA: "1234",
|
sdkGitSHA: "1234",
|
||||||
maxUploadSize: ServiceLocator.shared.settings.bugReportMaxUploadSize,
|
maxUploadSize: ServiceLocator.shared.settings.bugReportMaxUploadSize,
|
||||||
session: .mock,
|
session: .mock,
|
||||||
appHooks: AppHooks())
|
appHooks: AppHooks())
|
||||||
|
XCTAssertTrue(service.isEnabled)
|
||||||
|
XCTAssertFalse(service.crashedLastRun)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInitialStateWithRealServiceAndNoURL() throws {
|
||||||
|
let service = BugReportService(baseURL: nil,
|
||||||
|
applicationID: "mock_app_id",
|
||||||
|
sdkGitSHA: "1234",
|
||||||
|
maxUploadSize: ServiceLocator.shared.settings.bugReportMaxUploadSize,
|
||||||
|
session: .mock,
|
||||||
|
appHooks: AppHooks())
|
||||||
|
XCTAssertFalse(service.isEnabled)
|
||||||
XCTAssertFalse(service.crashedLastRun)
|
XCTAssertFalse(service.crashedLastRun)
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor func testSubmitBugReportWithRealService() async throws {
|
@MainActor func testSubmitBugReportWithRealService() async throws {
|
||||||
let service = BugReportService(withBaseURL: "https://www.example.com",
|
let service = BugReportService(baseURL: "https://www.example.com",
|
||||||
applicationId: "mock_app_id",
|
applicationID: "mock_app_id",
|
||||||
sdkGitSHA: "1234",
|
sdkGitSHA: "1234",
|
||||||
maxUploadSize: ServiceLocator.shared.settings.bugReportMaxUploadSize,
|
maxUploadSize: ServiceLocator.shared.settings.bugReportMaxUploadSize,
|
||||||
session: .mock,
|
session: .mock,
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
|||||||
completionSuggestionService: mockCompletionSuggestionService,
|
completionSuggestionService: mockCompletionSuggestionService,
|
||||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
||||||
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
composerDraftService: draftServiceMock)
|
composerDraftService: draftServiceMock)
|
||||||
|
|
||||||
@@ -684,6 +685,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
|||||||
completionSuggestionService: mockCompletionSuggestionService,
|
completionSuggestionService: mockCompletionSuggestionService,
|
||||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
||||||
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
composerDraftService: draftServiceMock)
|
composerDraftService: draftServiceMock)
|
||||||
|
|
||||||
@@ -727,6 +729,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
|||||||
completionSuggestionService: mockCompletionSuggestionService,
|
completionSuggestionService: mockCompletionSuggestionService,
|
||||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
||||||
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
composerDraftService: draftServiceMock)
|
composerDraftService: draftServiceMock)
|
||||||
|
|
||||||
@@ -760,6 +763,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
|||||||
completionSuggestionService: mockCompletionSuggestionService,
|
completionSuggestionService: mockCompletionSuggestionService,
|
||||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
||||||
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
composerDraftService: draftServiceMock)
|
composerDraftService: draftServiceMock)
|
||||||
|
|
||||||
@@ -794,6 +798,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
|||||||
completionSuggestionService: completionSuggestionServiceMock,
|
completionSuggestionService: completionSuggestionServiceMock,
|
||||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
|
||||||
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
composerDraftService: draftServiceMock)
|
composerDraftService: draftServiceMock)
|
||||||
viewModel.context.composerFormattingEnabled = true
|
viewModel.context.composerFormattingEnabled = true
|
||||||
|
|||||||
@@ -51,4 +51,24 @@ final class MapTilerURLBuilderTests: XCTestCase {
|
|||||||
let expectedURL: URL = "http://www.foo.com/dea61faf-292b-4774-9660-58fcef89a7f3/style.json?key=some_key"
|
let expectedURL: URL = "http://www.foo.com/dea61faf-292b-4774-9660-58fcef89a7f3/style.json?key=some_key"
|
||||||
XCTAssertEqual(url, expectedURL)
|
XCTAssertEqual(url, expectedURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testNilAPIKey() {
|
||||||
|
let configuration = MapTilerConfiguration(baseURL: Self.baseURL,
|
||||||
|
apiKey: nil,
|
||||||
|
lightStyleID: Self.lightStyleID,
|
||||||
|
darkStyleID: Self.darkStyleID)
|
||||||
|
XCTAssertFalse(configuration.isEnabled)
|
||||||
|
|
||||||
|
builder = configuration
|
||||||
|
|
||||||
|
let staticMapURL = builder.staticMapURL(for: .dark,
|
||||||
|
coordinates: .init(latitude: 1, longitude: 2),
|
||||||
|
zoomLevel: 5,
|
||||||
|
size: .init(width: 300, height: 200),
|
||||||
|
attribution: .topLeft)
|
||||||
|
XCTAssertNil(staticMapURL)
|
||||||
|
|
||||||
|
let dynamicMapURL = builder.dynamicMapURL(for: .light)
|
||||||
|
XCTAssertNil(dynamicMapURL)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ class SettingsScreenViewModelTests: XCTestCase {
|
|||||||
@MainActor override func setUpWithError() throws {
|
@MainActor override func setUpWithError() throws {
|
||||||
cancellables.removeAll()
|
cancellables.removeAll()
|
||||||
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: ""))))
|
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: ""))))
|
||||||
viewModel = SettingsScreenViewModel(userSession: userSession)
|
viewModel = SettingsScreenViewModel(userSession: userSession,
|
||||||
|
appSettings: ServiceLocator.shared.settings,
|
||||||
|
isBugReportServiceEnabled: true)
|
||||||
context = viewModel.context
|
context = viewModel.context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
|
|||||||
userSessionFlowCoordinator = UserSessionFlowCoordinator(userSession: UserSessionMock(.init(clientProxy: clientProxy)),
|
userSessionFlowCoordinator = UserSessionFlowCoordinator(userSession: UserSessionMock(.init(clientProxy: clientProxy)),
|
||||||
navigationRootCoordinator: navigationRootCoordinator,
|
navigationRootCoordinator: navigationRootCoordinator,
|
||||||
appLockService: AppLockServiceMock(),
|
appLockService: AppLockServiceMock(),
|
||||||
bugReportService: BugReportServiceMock(),
|
bugReportService: BugReportServiceMock(.init()),
|
||||||
elementCallService: ElementCallServiceMock(.init()),
|
elementCallService: ElementCallServiceMock(.init()),
|
||||||
timelineControllerFactory: timelineControllerFactory,
|
timelineControllerFactory: timelineControllerFactory,
|
||||||
appMediator: AppMediatorMock.default,
|
appMediator: AppMediatorMock.default,
|
||||||
|
|||||||
Reference in New Issue
Block a user