mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Fixes vector-im/element-x-ios/issues/117 - Event permalink timeline action
* moved NSRegularExpression outside of the AttributedString builder into the MatrixEntityRegex * fixed eventId v3 regex * added permalink builders for users, room identifiers and aliases, and events * added timeline item permalink contextual menu actions and error alerts * added an app wide ServiceLocator and moved the top level userIndicatorPresenter to it. * added URL constructor that takes a StaticString and returns an non-optional * Include Unit and UI tests in the swiftlint search paths
This commit is contained in:
@@ -11,6 +11,8 @@
|
||||
"room_timeline_style_plain_long_description" = "Plain Timeline";
|
||||
"room_timeline_style_bubbled_long_description" = "Bubbled Timeline";
|
||||
|
||||
"room_timeline_permalink_creation_failure" = "Failed creating the permalink";
|
||||
|
||||
// MARK: - Authentication
|
||||
|
||||
"authentication_login_title" = "Welcome back!";
|
||||
|
||||
@@ -18,6 +18,19 @@ import Combine
|
||||
import MatrixRustSDK
|
||||
import UIKit
|
||||
|
||||
struct ServiceLocator {
|
||||
fileprivate static var serviceLocator: ServiceLocator?
|
||||
static var shared: ServiceLocator {
|
||||
guard let serviceLocator = serviceLocator else {
|
||||
fatalError("The service locator should be setup at this point")
|
||||
}
|
||||
|
||||
return serviceLocator
|
||||
}
|
||||
|
||||
let userIndicatorPresenter: UserIndicatorTypePresenter
|
||||
}
|
||||
|
||||
class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
private let window: UIWindow
|
||||
|
||||
@@ -38,7 +51,6 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
private let screenshotDetector: ScreenshotDetector
|
||||
private let backgroundTaskService: BackgroundTaskServiceProtocol
|
||||
|
||||
private var indicatorPresenter: UserIndicatorTypePresenterProtocol
|
||||
private var loadingIndicator: UserIndicator?
|
||||
private var statusIndicator: UserIndicator?
|
||||
|
||||
@@ -46,13 +58,8 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
|
||||
init() {
|
||||
stateMachine = AppCoordinatorStateMachine()
|
||||
|
||||
do {
|
||||
bugReportService = try BugReportService(withBaseUrlString: BuildSettings.bugReportServiceBaseUrlString,
|
||||
sentryEndpoint: BuildSettings.bugReportSentryEndpoint)
|
||||
} catch {
|
||||
fatalError(error.localizedDescription)
|
||||
}
|
||||
|
||||
bugReportService = BugReportService(withBaseURL: BuildSettings.bugReportServiceBaseURL, sentryURL: BuildSettings.bugReportSentryURL)
|
||||
|
||||
splashViewController = SplashViewController()
|
||||
mainNavigationController = ElementNavigationController(rootViewController: splashViewController)
|
||||
@@ -64,7 +71,7 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
|
||||
memberDetailProviderManager = MemberDetailProviderManager()
|
||||
|
||||
indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: mainNavigationController)
|
||||
ServiceLocator.serviceLocator = ServiceLocator(userIndicatorPresenter: UserIndicatorTypePresenter(presentingViewController: mainNavigationController))
|
||||
|
||||
guard let bundleIdentifier = Bundle.main.bundleIdentifier else {
|
||||
fatalError("Should have a valid bundle identifier at this point")
|
||||
@@ -256,6 +263,7 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
attributedStringBuilder: AttributedStringBuilder())
|
||||
|
||||
let timelineController = RoomTimelineController(userId: userId,
|
||||
roomId: roomIdentifier,
|
||||
timelineProvider: RoomTimelineProvider(roomProxy: roomProxy),
|
||||
timelineItemFactory: timelineItemFactory,
|
||||
mediaProvider: userSession.mediaProvider,
|
||||
@@ -409,7 +417,7 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
// MARK: Toasts and loading indicators
|
||||
|
||||
private func showLoadingIndicator() {
|
||||
loadingIndicator = indicatorPresenter.present(.loading(label: ElementL10n.loading, isInteractionBlocking: true))
|
||||
loadingIndicator = ServiceLocator.shared.userIndicatorPresenter.present(.loading(label: ElementL10n.loading, isInteractionBlocking: true))
|
||||
}
|
||||
|
||||
private func hideLoadingIndicator() {
|
||||
@@ -417,10 +425,10 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
}
|
||||
|
||||
private func showLoginErrorToast() {
|
||||
statusIndicator = indicatorPresenter.present(.error(label: "Failed logging in"))
|
||||
statusIndicator = ServiceLocator.shared.userIndicatorPresenter.present(.error(label: "Failed logging in"))
|
||||
}
|
||||
|
||||
private func showLogoutErrorToast() {
|
||||
statusIndicator = indicatorPresenter.present(.error(label: "Failed logging out"))
|
||||
statusIndicator = ServiceLocator.shared.userIndicatorPresenter.present(.error(label: "Failed logging out"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,14 +23,14 @@ final class BuildSettings {
|
||||
|
||||
// MARK: - Bug report
|
||||
|
||||
static let bugReportServiceBaseUrlString = "https://riot.im/bugreports"
|
||||
static let bugReportSentryEndpoint = "https://f39ac49e97714316965b777d9f3d6cd8@sentry.tools.element.io/44"
|
||||
static let bugReportServiceBaseURL = URL(staticString: "https://riot.im/bugreports")
|
||||
static let bugReportSentryURL = URL(staticString: "https://f39ac49e97714316965b777d9f3d6cd8@sentry.tools.element.io/44")
|
||||
// Use the name allocated by the bug report server
|
||||
static let bugReportApplicationId = "riot-ios"
|
||||
static let bugReportUISIId = "element-auto-uisi"
|
||||
|
||||
static let bugReportGHLabels = ["Element-X"]
|
||||
|
||||
|
||||
// MARK: - Analytics
|
||||
|
||||
#if DEBUG
|
||||
@@ -57,4 +57,8 @@ final class BuildSettings {
|
||||
// MARK: - Room screen
|
||||
|
||||
static let defaultRoomTimelineStyle: TimelineStyle = .bubbles
|
||||
|
||||
// MARK: - Other
|
||||
|
||||
static var permalinkBaseURL = URL(staticString: "https://matrix.to")
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ extension ElementL10n {
|
||||
public static let authenticationServerInfoMatrixDescription = ElementL10n.tr("Untranslated", "authentication_server_info_matrix_description")
|
||||
/// Choose your server to store your data
|
||||
public static let authenticationServerInfoTitle = ElementL10n.tr("Untranslated", "authentication_server_info_title")
|
||||
/// Failed creating the permalink
|
||||
public static let roomTimelinePermalinkCreationFailure = ElementL10n.tr("Untranslated", "room_timeline_permalink_creation_failure")
|
||||
/// Bubbled Timeline
|
||||
public static let roomTimelineStyleBubbledLongDescription = ElementL10n.tr("Untranslated", "room_timeline_style_bubbled_long_description")
|
||||
/// Plain Timeline
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension URL {
|
||||
init(staticString: StaticString) {
|
||||
guard let url = URL(string: "\(staticString)") else {
|
||||
fatalError("The static string used to create this URL is invalid")
|
||||
}
|
||||
|
||||
self = url
|
||||
}
|
||||
}
|
||||
@@ -22,24 +22,6 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
||||
private let temporaryCodeBlockMarkingColor = UIColor.cyan
|
||||
private let linkColor = UIColor.blue
|
||||
|
||||
private let userIdDetector: NSRegularExpression
|
||||
private let roomIdDetector: NSRegularExpression
|
||||
private let eventIdDetector: NSRegularExpression
|
||||
private let roomAliasDetector: NSRegularExpression
|
||||
private let linkDetector: NSDataDetector
|
||||
|
||||
init() {
|
||||
do {
|
||||
userIdDetector = try NSRegularExpression(pattern: MatrixEntityRegex.userId.rawValue, options: .caseInsensitive)
|
||||
roomIdDetector = try NSRegularExpression(pattern: MatrixEntityRegex.roomId.rawValue, options: .caseInsensitive)
|
||||
eventIdDetector = try NSRegularExpression(pattern: MatrixEntityRegex.eventId.rawValue, options: .caseInsensitive)
|
||||
roomAliasDetector = try NSRegularExpression(pattern: MatrixEntityRegex.roomAlias.rawValue, options: .caseInsensitive)
|
||||
linkDetector = try NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
|
||||
} catch {
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
func fromPlain(_ string: String?) async -> AttributedString? {
|
||||
await Task.detached {
|
||||
fromPlain(string)
|
||||
@@ -181,11 +163,11 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
||||
let string = attributedString.string
|
||||
let range = NSRange(location: 0, length: attributedString.string.count)
|
||||
|
||||
var matches = userIdDetector.matches(in: string, options: [], range: range)
|
||||
matches.append(contentsOf: roomIdDetector.matches(in: string, options: [], range: range))
|
||||
matches.append(contentsOf: eventIdDetector.matches(in: string, options: [], range: range))
|
||||
matches.append(contentsOf: roomAliasDetector.matches(in: string, options: [], range: range))
|
||||
matches.append(contentsOf: linkDetector.matches(in: string, options: [], range: range))
|
||||
var matches = MatrixEntityRegex.userIdentifierRegex.matches(in: string, options: [], range: range)
|
||||
matches.append(contentsOf: MatrixEntityRegex.roomIdentifierRegex.matches(in: string, options: [], range: range))
|
||||
matches.append(contentsOf: MatrixEntityRegex.eventIdentifierRegex.matches(in: string, options: [], range: range))
|
||||
matches.append(contentsOf: MatrixEntityRegex.roomAliasRegex.matches(in: string, options: [], range: range))
|
||||
matches.append(contentsOf: MatrixEntityRegex.linkRegex.matches(in: string, options: [], range: range))
|
||||
|
||||
guard matches.count > 0 else {
|
||||
return
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
// https://spec.matrix.org/latest/appendices/#identifier-grammar
|
||||
enum MatrixEntityRegex: String {
|
||||
case homeserver
|
||||
case userId
|
||||
@@ -34,7 +35,56 @@ enum MatrixEntityRegex: String {
|
||||
case .roomId:
|
||||
return "![A-Z0-9]+:" + MatrixEntityRegex.homeserver.rawValue
|
||||
case .eventId:
|
||||
return "\\$[A-Z0-9]+:" + MatrixEntityRegex.homeserver.rawValue
|
||||
return "\\$[A-Z0-9\\/+]+"
|
||||
}
|
||||
}
|
||||
|
||||
// swiftlint:disable force_try
|
||||
static var homeserverRegex = try! NSRegularExpression(pattern: MatrixEntityRegex.homeserver.rawValue, options: .caseInsensitive)
|
||||
static var userIdentifierRegex = try! NSRegularExpression(pattern: MatrixEntityRegex.userId.rawValue, options: .caseInsensitive)
|
||||
static var roomAliasRegex = try! NSRegularExpression(pattern: MatrixEntityRegex.roomAlias.rawValue, options: .caseInsensitive)
|
||||
static var roomIdentifierRegex = try! NSRegularExpression(pattern: MatrixEntityRegex.roomId.rawValue, options: .caseInsensitive)
|
||||
static var eventIdentifierRegex = try! NSRegularExpression(pattern: MatrixEntityRegex.eventId.rawValue, options: .caseInsensitive)
|
||||
static var linkRegex = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
|
||||
// swiftlint:enable force_try
|
||||
|
||||
static func isMatrixHomeserver(_ homeserver: String) -> Bool {
|
||||
guard let match = userIdentifierRegex.firstMatch(in: homeserver, range: .init(location: 0, length: homeserver.count)) else {
|
||||
return false
|
||||
}
|
||||
|
||||
return match.range.length == homeserver.count
|
||||
}
|
||||
|
||||
static func isMatrixUserIdentifier(_ identifier: String) -> Bool {
|
||||
guard let match = userIdentifierRegex.firstMatch(in: identifier, range: .init(location: 0, length: identifier.count)) else {
|
||||
return false
|
||||
}
|
||||
|
||||
return match.range.length == identifier.count
|
||||
}
|
||||
|
||||
static func isMatrixRoomAlias(_ alias: String) -> Bool {
|
||||
guard let match = roomAliasRegex.firstMatch(in: alias, range: .init(location: 0, length: alias.count)) else {
|
||||
return false
|
||||
}
|
||||
|
||||
return match.range.length == alias.count
|
||||
}
|
||||
|
||||
static func isMatrixRoomIdentifier(_ identifier: String) -> Bool {
|
||||
guard let match = roomIdentifierRegex.firstMatch(in: identifier, range: .init(location: 0, length: identifier.count)) else {
|
||||
return false
|
||||
}
|
||||
|
||||
return match.range.length == identifier.count
|
||||
}
|
||||
|
||||
static func isMatrixEventIdentifier(_ identifier: String) -> Bool {
|
||||
guard let match = eventIdentifierRegex.firstMatch(in: identifier, range: .init(location: 0, length: identifier.count)) else {
|
||||
return false
|
||||
}
|
||||
|
||||
return match.range.length == identifier.count
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum PermalinkBuilderError: Error {
|
||||
case invalidUserIdentifier
|
||||
case invalidRoomIdentifier
|
||||
case invalidRoomAlias
|
||||
case invalidEventIdentifier
|
||||
case failedConstructingURL
|
||||
case failedAddingPercentEncoding
|
||||
}
|
||||
|
||||
enum PermalinkBuilder {
|
||||
static var uriComponentCharacterSet: CharacterSet = {
|
||||
var charset = CharacterSet.alphanumerics
|
||||
charset.insert(charactersIn: "-_.!~*'()")
|
||||
return charset
|
||||
}()
|
||||
|
||||
static func permalinkTo(userIdentifier: String) throws -> URL {
|
||||
guard MatrixEntityRegex.isMatrixUserIdentifier(userIdentifier) else {
|
||||
throw PermalinkBuilderError.invalidUserIdentifier
|
||||
}
|
||||
|
||||
let urlString = "\(BuildSettings.permalinkBaseURL)/#/\(userIdentifier)"
|
||||
|
||||
guard let url = URL(string: urlString) else {
|
||||
throw PermalinkBuilderError.failedConstructingURL
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
static func permalinkTo(roomIdentifier: String) throws -> URL {
|
||||
guard MatrixEntityRegex.isMatrixRoomIdentifier(roomIdentifier) else {
|
||||
throw PermalinkBuilderError.invalidRoomIdentifier
|
||||
}
|
||||
|
||||
return try permalinkTo(roomIdentifierOrAlias: roomIdentifier)
|
||||
}
|
||||
|
||||
static func permalinkTo(roomAlias: String) throws -> URL {
|
||||
guard MatrixEntityRegex.isMatrixRoomAlias(roomAlias) else {
|
||||
throw PermalinkBuilderError.invalidRoomAlias
|
||||
}
|
||||
|
||||
return try permalinkTo(roomIdentifierOrAlias: roomAlias)
|
||||
}
|
||||
|
||||
static func permalinkTo(eventIdentifier: String, roomIdentifier: String) throws -> URL {
|
||||
guard MatrixEntityRegex.isMatrixEventIdentifier(eventIdentifier) else {
|
||||
throw PermalinkBuilderError.invalidEventIdentifier
|
||||
}
|
||||
guard MatrixEntityRegex.isMatrixRoomIdentifier(roomIdentifier) else {
|
||||
throw PermalinkBuilderError.invalidRoomIdentifier
|
||||
}
|
||||
|
||||
guard let roomId = roomIdentifier.addingPercentEncoding(withAllowedCharacters: uriComponentCharacterSet),
|
||||
let eventId = eventIdentifier.addingPercentEncoding(withAllowedCharacters: uriComponentCharacterSet) else {
|
||||
throw PermalinkBuilderError.failedAddingPercentEncoding
|
||||
}
|
||||
|
||||
let urlString = "\(BuildSettings.permalinkBaseURL)/#/\(roomId)/\(eventId)"
|
||||
|
||||
guard let url = URL(string: urlString) else {
|
||||
throw PermalinkBuilderError.failedConstructingURL
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private static func permalinkTo(roomIdentifierOrAlias: String) throws -> URL {
|
||||
guard let identifier = roomIdentifierOrAlias.addingPercentEncoding(withAllowedCharacters: uriComponentCharacterSet) else {
|
||||
throw PermalinkBuilderError.failedAddingPercentEncoding
|
||||
}
|
||||
|
||||
let urlString = "\(BuildSettings.permalinkBaseURL)/#/\(identifier)"
|
||||
|
||||
guard let url = URL(string: urlString) else {
|
||||
throw PermalinkBuilderError.failedConstructingURL
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
}
|
||||
@@ -64,8 +64,10 @@ extension LoginHomeserver {
|
||||
|
||||
/// A mock homeserver that supports only supports authentication via a single SSO provider.
|
||||
static var mockOIDC: LoginHomeserver {
|
||||
// swiftlint:disable:next force_unwrapping
|
||||
let issuerURL = URL(string: "https://auth.company.com")!
|
||||
guard let issuerURL = URL(string: "https://auth.company.com") else {
|
||||
fatalError("This shoud never fail parsing")
|
||||
}
|
||||
|
||||
return LoginHomeserver(address: "company.com", loginMode: .oidc(issuerURL))
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ enum RoomScreenViewModelAction { }
|
||||
enum TimelineItemContextMenuAction: Hashable {
|
||||
case copy
|
||||
case quote
|
||||
case copyPermalink
|
||||
}
|
||||
|
||||
enum RoomScreenViewAction {
|
||||
@@ -49,4 +50,12 @@ struct RoomScreenViewState: BindableState {
|
||||
|
||||
struct RoomScreenViewStateBindings {
|
||||
var composerText: String
|
||||
|
||||
/// Information describing the currently displayed alert.
|
||||
var alertInfo: AlertInfo<RoomScreenErrorType>?
|
||||
}
|
||||
|
||||
enum RoomScreenErrorType: Hashable {
|
||||
/// A specific error message shown in an alert.
|
||||
case alert(String)
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
return []
|
||||
}
|
||||
|
||||
return [.copy, .quote]
|
||||
return [.copy, .quote, .copyPermalink]
|
||||
}
|
||||
|
||||
private func processContentMenuAction(_ action: TimelineItemContextMenuAction, itemId: String) {
|
||||
@@ -130,6 +130,22 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
UIPasteboard.general.string = item.text
|
||||
case .quote:
|
||||
state.bindings.composerText = "> \(item.text)"
|
||||
case .copyPermalink:
|
||||
do {
|
||||
let permalink = try PermalinkBuilder.permalinkTo(eventIdentifier: item.id, roomIdentifier: timelineController.roomId)
|
||||
UIPasteboard.general.url = permalink
|
||||
} catch {
|
||||
displayError(.alert(ElementL10n.roomTimelinePermalinkCreationFailure))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func displayError(_ type: RoomScreenErrorType) {
|
||||
switch type {
|
||||
case .alert(let message):
|
||||
state.bindings.alertInfo = AlertInfo(id: type,
|
||||
title: ElementL10n.dialogTitleError,
|
||||
message: message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ struct RoomScreen: View {
|
||||
RoomHeaderView(context: context)
|
||||
}
|
||||
}
|
||||
.alert(item: $context.alertInfo) { $0.alert }
|
||||
}
|
||||
|
||||
private func sendMessage() {
|
||||
|
||||
@@ -25,11 +25,15 @@ public struct TimelineItemContextMenu: View {
|
||||
ForEach(contextMenuActions, id: \.self) { item in
|
||||
switch item {
|
||||
case .copy:
|
||||
Button("Copy") {
|
||||
Button(ElementL10n.actionCopy) {
|
||||
callback(item)
|
||||
}
|
||||
case .quote:
|
||||
Button("Quote") {
|
||||
Button(ElementL10n.actionQuote) {
|
||||
callback(item)
|
||||
}
|
||||
case .copyPermalink:
|
||||
Button(ElementL10n.permalink) {
|
||||
callback(item)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,7 @@ class OIDCService {
|
||||
|
||||
private var metadata: OIDServiceConfiguration?
|
||||
/// Redirect URI for the request. Must match the `client_uri` in reverse DNS format.
|
||||
private let redirectURI = URL(string: "io.element:/callback")!
|
||||
// swiftlint:disable:previous force_unwrapping
|
||||
private var redirectURI = URL(staticString: "io.element:/callback")
|
||||
|
||||
/// Maintains a strong ref to the authorization session that's in progress.
|
||||
private var session: OIDExternalUserAgentSession?
|
||||
|
||||
@@ -20,40 +20,29 @@ import MatrixRustSDK
|
||||
import Sentry
|
||||
import UIKit
|
||||
|
||||
enum BugReportServiceError: Error {
|
||||
case invalidBaseUrlString
|
||||
case invalidSentryEndpoint
|
||||
}
|
||||
|
||||
class BugReportService: BugReportServiceProtocol {
|
||||
private let baseURL: URL
|
||||
private let sentryEndpoint: String
|
||||
private let sentryURL: URL
|
||||
private let applicationId: String
|
||||
private let session: URLSession
|
||||
private var lastCrashEventId: String?
|
||||
|
||||
init(withBaseUrlString baseUrlString: String,
|
||||
sentryEndpoint: String,
|
||||
|
||||
init(withBaseURL baseURL: URL,
|
||||
sentryURL: URL,
|
||||
applicationId: String = BuildSettings.bugReportApplicationId,
|
||||
session: URLSession = .shared) throws {
|
||||
guard let url = URL(string: baseUrlString) else {
|
||||
throw BugReportServiceError.invalidBaseUrlString
|
||||
}
|
||||
guard !sentryEndpoint.isEmpty else {
|
||||
throw BugReportServiceError.invalidSentryEndpoint
|
||||
}
|
||||
baseURL = url
|
||||
self.sentryEndpoint = sentryEndpoint
|
||||
session: URLSession = .shared) {
|
||||
self.baseURL = baseURL
|
||||
self.sentryURL = sentryURL
|
||||
self.applicationId = applicationId
|
||||
self.session = session
|
||||
|
||||
|
||||
// enable SentrySDK
|
||||
SentrySDK.start { options in
|
||||
#if DEBUG
|
||||
options.enabled = false
|
||||
#endif
|
||||
|
||||
options.dsn = sentryEndpoint
|
||||
options.dsn = sentryURL.absoluteString
|
||||
|
||||
// Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
|
||||
// We recommend adjusting this value in production.
|
||||
|
||||
@@ -18,6 +18,8 @@ import Combine
|
||||
import Foundation
|
||||
|
||||
class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||
let roomId = "MockRoomIdentifier"
|
||||
|
||||
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
|
||||
|
||||
var timelineItems: [RoomTimelineItemProtocol] = [SeparatorRoomTimelineItem(id: UUID().uuidString,
|
||||
|
||||
@@ -32,16 +32,19 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
let roomId: String
|
||||
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
|
||||
|
||||
private(set) var timelineItems = [RoomTimelineItemProtocol]()
|
||||
|
||||
init(userId: String,
|
||||
roomId: String,
|
||||
timelineProvider: RoomTimelineProviderProtocol,
|
||||
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
||||
mediaProvider: MediaProviderProtocol,
|
||||
memberDetailProvider: MemberDetailProviderProtocol) {
|
||||
self.userId = userId
|
||||
self.roomId = roomId
|
||||
self.timelineProvider = timelineProvider
|
||||
self.timelineItemFactory = timelineItemFactory
|
||||
self.mediaProvider = mediaProvider
|
||||
|
||||
@@ -28,6 +28,8 @@ enum RoomTimelineControllerError: Error {
|
||||
|
||||
@MainActor
|
||||
protocol RoomTimelineControllerProtocol {
|
||||
var roomId: String { get }
|
||||
|
||||
var timelineItems: [RoomTimelineItemProtocol] { get }
|
||||
var callbacks: PassthroughSubject<RoomTimelineControllerCallback, Never> { get }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user