Expose sliding sync proxy URL on the server selection screen; make app more resilient to slidinc sync configuration errors, remove fatal errors

This commit is contained in:
Stefan Ceriu
2022-11-17 09:33:39 +02:00
committed by Stefan Ceriu
parent 2fd0491a18
commit 276daf0355
10 changed files with 88 additions and 55 deletions
@@ -20,8 +20,9 @@ final class BuildSettings {
// MARK: - Servers // MARK: - Servers
static let defaultHomeserverAddress = "matrix.org" static let defaultHomeserverAddress = "matrix.org"
static let slidingSyncProxyBaseURL = URL(staticString: "https://slidingsync.lab.element.dev")
static let defaultSlidingSyncProxyBaseURLString = "https://slidingsync.lab.element.dev"
// MARK: - Bug report // MARK: - Bug report
static let bugReportServiceBaseURL = URL(staticString: "https://riot.im/bugreports") static let bugReportServiceBaseURL = URL(staticString: "https://riot.im/bugreports")
+10 -1
View File
@@ -25,13 +25,17 @@ final class ElementSettings: ObservableObject {
case timelineStyle case timelineStyle
case enableAnalytics case enableAnalytics
case isIdentifiedForAnalytics case isIdentifiedForAnalytics
case slidingSyncProxyBaseURLString
} }
static let shared = ElementSettings() static let shared = ElementSettings()
/// UserDefaults to be used on reads and writes. /// UserDefaults to be used on reads and writes.
static var store: UserDefaults { static var store: UserDefaults {
.standard guard let userDefaults = UserDefaults(suiteName: ElementInfoPlist.appGroupIdentifier) else {
fatalError("Fail to load shared UserDefaults")
}
return userDefaults
} }
private init() { private init() {
@@ -59,4 +63,9 @@ final class ElementSettings: ObservableObject {
@AppStorage(UserDefaultsKeys.timelineStyle.rawValue, store: store) @AppStorage(UserDefaultsKeys.timelineStyle.rawValue, store: store)
var timelineStyle = BuildSettings.defaultRoomTimelineStyle var timelineStyle = BuildSettings.defaultRoomTimelineStyle
// MARK: - Client
@AppStorage(UserDefaultsKeys.slidingSyncProxyBaseURLString.rawValue, store: store)
var slidingSyncProxyBaseURLString = BuildSettings.defaultSlidingSyncProxyBaseURLString
} }
@@ -59,6 +59,8 @@ struct ServerSelectionViewState: BindableState {
struct ServerSelectionBindings { struct ServerSelectionBindings {
/// The homeserver address input by the user. /// The homeserver address input by the user.
var homeserverAddress: String var homeserverAddress: String
/// The sliding sync proxy address input by the user.
var slidingSyncProxyAddress: String
/// Information describing the currently displayed alert. /// Information describing the currently displayed alert.
var alertInfo: AlertInfo<ServerSelectionErrorType>? var alertInfo: AlertInfo<ServerSelectionErrorType>?
} }
@@ -30,7 +30,9 @@ class ServerSelectionViewModel: ServerSelectionViewModelType, ServerSelectionVie
// MARK: - Setup // MARK: - Setup
init(homeserverAddress: String, isModallyPresented: Bool) { init(homeserverAddress: String, isModallyPresented: Bool) {
let bindings = ServerSelectionBindings(homeserverAddress: homeserverAddress) let bindings = ServerSelectionBindings(homeserverAddress: homeserverAddress,
slidingSyncProxyAddress: ElementSettings.shared.slidingSyncProxyBaseURLString)
super.init(initialViewState: ServerSelectionViewState(bindings: bindings, super.init(initialViewState: ServerSelectionViewState(bindings: bindings,
isModallyPresented: isModallyPresented)) isModallyPresented: isModallyPresented))
} }
@@ -40,6 +42,10 @@ class ServerSelectionViewModel: ServerSelectionViewModelType, ServerSelectionVie
override func process(viewAction: ServerSelectionViewAction) async { override func process(viewAction: ServerSelectionViewAction) async {
switch viewAction { switch viewAction {
case .confirm: case .confirm:
if !state.bindings.slidingSyncProxyAddress.isEmpty {
ElementSettings.shared.slidingSyncProxyBaseURLString = state.bindings.slidingSyncProxyAddress
}
callback?(.confirm(homeserverAddress: state.bindings.homeserverAddress)) callback?(.confirm(homeserverAddress: state.bindings.homeserverAddress))
case .dismiss: case .dismiss:
callback?(.dismiss) callback?(.dismiss)
@@ -17,18 +17,8 @@
import SwiftUI import SwiftUI
struct ServerSelectionScreen: View { struct ServerSelectionScreen: View {
// MARK: - Properties
// MARK: Private
@FocusState var isTextFieldFocused: Bool
// MARK: Public
@ObservedObject var context: ServerSelectionViewModel.Context @ObservedObject var context: ServerSelectionViewModel.Context
// MARK: Views
var body: some View { var body: some View {
ScrollView { ScrollView {
VStack(spacing: 0) { VStack(spacing: 0) {
@@ -69,8 +59,8 @@ struct ServerSelectionScreen: View {
var serverForm: some View { var serverForm: some View {
VStack(alignment: .leading, spacing: 12) { VStack(alignment: .leading, spacing: 12) {
TextField(ElementL10n.ftueAuthChooseServerEntryHint, text: $context.homeserverAddress) TextField(ElementL10n.ftueAuthChooseServerEntryHint, text: $context.homeserverAddress)
.focused($isTextFieldFocused) .textFieldStyle(.elementInput(labelText: ElementL10n.hsUrl,
.textFieldStyle(.elementInput(footerText: context.viewState.footerMessage, footerText: context.viewState.footerMessage,
isError: context.viewState.isShowingFooterError)) isError: context.viewState.isShowingFooterError))
.keyboardType(.URL) .keyboardType(.URL)
.autocapitalization(.none) .autocapitalization(.none)
@@ -80,6 +70,17 @@ struct ServerSelectionScreen: View {
.onSubmit(submit) .onSubmit(submit)
.accessibilityIdentifier("addressTextField") .accessibilityIdentifier("addressTextField")
Divider()
TextField(ElementL10n.ftueAuthChooseServerEntryHint, text: $context.slidingSyncProxyAddress)
.textFieldStyle(.elementInput(labelText: "Sliding sync proxy URL"))
.keyboardType(.URL)
.autocapitalization(.none)
.disableAutocorrection(true)
.submitLabel(.done)
.onSubmit(submit)
.accessibilityIdentifier("slidingSyncProxyAddressTextField")
Button(action: submit) { Button(action: submit) {
Text(context.viewState.buttonTitle) Text(context.viewState.buttonTitle)
} }
@@ -21,7 +21,7 @@ typealias HomeScreenViewModelType = StateStoreViewModel<HomeScreenViewState, Hom
class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol { class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol {
private let userSession: UserSessionProtocol private let userSession: UserSessionProtocol
private let roomSummaryProvider: RoomSummaryProviderProtocol private let roomSummaryProvider: RoomSummaryProviderProtocol?
private let attributedStringBuilder: AttributedStringBuilderProtocol private let attributedStringBuilder: AttributedStringBuilderProtocol
private var roomsForIdentifiers = [String: HomeScreenRoom]() private var roomsForIdentifiers = [String: HomeScreenRoom]()
@@ -51,6 +51,25 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
} }
.store(in: &cancellables) .store(in: &cancellables)
Task {
if case let .success(userAvatarURLString) = await userSession.clientProxy.loadUserAvatarURLString() {
if case let .success(avatar) = await userSession.mediaProvider.loadImageFromURLString(userAvatarURLString, avatarSize: .user(on: .home)) {
state.userAvatar = avatar
}
}
}
Task {
if case let .success(userDisplayName) = await userSession.clientProxy.loadUserDisplayName() {
state.userDisplayName = userDisplayName
}
}
guard let roomSummaryProvider else {
MXLog.error("Room summary provider unavailable")
return
}
Publishers.CombineLatest3(roomSummaryProvider.statePublisher, Publishers.CombineLatest3(roomSummaryProvider.statePublisher,
roomSummaryProvider.countPublisher, roomSummaryProvider.countPublisher,
roomSummaryProvider.roomListPublisher) roomSummaryProvider.roomListPublisher)
@@ -77,20 +96,6 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
} }
.store(in: &cancellables) .store(in: &cancellables)
Task {
if case let .success(userAvatarURLString) = await userSession.clientProxy.loadUserAvatarURLString() {
if case let .success(avatar) = await userSession.mediaProvider.loadImageFromURLString(userAvatarURLString, avatarSize: .user(on: .home)) {
state.userAvatar = avatar
}
}
}
Task {
if case let .success(userDisplayName) = await userSession.clientProxy.loadUserDisplayName() {
state.userDisplayName = userDisplayName
}
}
updateRooms() updateRooms()
} }
@@ -99,7 +104,9 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
override func process(viewAction: HomeScreenViewAction) async { override func process(viewAction: HomeScreenViewAction) async {
switch viewAction { switch viewAction {
case .loadRoomData(let roomIdentifier): case .loadRoomData(let roomIdentifier):
loadDataForRoomIdentifier(roomIdentifier) if state.roomListMode != .skeletons {
loadDataForRoomIdentifier(roomIdentifier)
}
case .selectRoom(let roomIdentifier): case .selectRoom(let roomIdentifier):
callback?(.selectRoom(roomIdentifier: roomIdentifier)) callback?(.selectRoom(roomIdentifier: roomIdentifier))
case .userMenu(let action): case .userMenu(let action):
@@ -118,6 +125,11 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
// MARK: - Private // MARK: - Private
private func loadDataForRoomIdentifier(_ identifier: String) { private func loadDataForRoomIdentifier(_ identifier: String) {
guard let roomSummaryProvider else {
MXLog.error("Room summary provider unavailable")
return
}
guard let roomSummary = roomSummaryProvider.roomListPublisher.value.first(where: { $0.asFilled?.id == identifier })?.asFilled, guard let roomSummary = roomSummaryProvider.roomListPublisher.value.first(where: { $0.asFilled?.id == identifier })?.asFilled,
let roomIndex = state.rooms.firstIndex(where: { $0.id == identifier }) else { let roomIndex = state.rooms.firstIndex(where: { $0.id == identifier }) else {
return return
@@ -140,6 +152,11 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
} }
private func updateRooms() { private func updateRooms() {
guard let roomSummaryProvider else {
MXLog.error("Room summary provider unavailable")
return
}
var rooms = [HomeScreenRoom]() var rooms = [HomeScreenRoom]()
var newRoomsForIdentifiers = [String: HomeScreenRoom]() var newRoomsForIdentifiers = [String: HomeScreenRoom]()
@@ -55,22 +55,16 @@ class ClientProxy: ClientProxyProtocol {
private let clientQueue: DispatchQueue private let clientQueue: DispatchQueue
private var slidingSyncObserverToken: StoppableSpawn? private var slidingSyncObserverToken: StoppableSpawn?
private var slidingSync: SlidingSync! private var slidingSync: SlidingSync?
var roomSummaryProviderInternal: RoomSummaryProviderProtocol! var roomSummaryProvider: RoomSummaryProviderProtocol?
var roomSummaryProvider: RoomSummaryProviderProtocol {
guard let roomSummaryProviderInternal else {
fatalError("There is an issue with ClientProxy object initialization")
}
return roomSummaryProviderInternal
}
deinit { deinit {
// These need to be inlined instead of using stopSync() // These need to be inlined instead of using stopSync()
// as we can't call async methods safely from deinit // as we can't call async methods safely from deinit
client.setDelegate(delegate: nil) client.setDelegate(delegate: nil)
slidingSyncObserverToken?.cancel() slidingSyncObserverToken?.cancel()
slidingSync.setObserver(observer: nil) slidingSync?.setObserver(observer: nil)
} }
let callbacks = PassthroughSubject<ClientProxyCallback, Never>() let callbacks = PassthroughSubject<ClientProxyCallback, Never>()
@@ -84,7 +78,7 @@ class ClientProxy: ClientProxyProtocol {
await Task.dispatch(on: clientQueue) { await Task.dispatch(on: clientQueue) {
do { do {
let slidingSyncBuilder = try client.slidingSync().homeserver(url: BuildSettings.slidingSyncProxyBaseURL.absoluteString) let slidingSyncBuilder = try client.slidingSync().homeserver(url: ElementSettings.shared.slidingSyncProxyBaseURLString)
let slidingSyncView = try SlidingSyncViewBuilder() let slidingSyncView = try SlidingSyncViewBuilder()
.timelineLimit(limit: 10) .timelineLimit(limit: 10)
@@ -94,16 +88,18 @@ class ClientProxy: ClientProxyProtocol {
.syncMode(mode: .fullSync) .syncMode(mode: .fullSync)
.build() .build()
self.slidingSync = try slidingSyncBuilder let slidingSync = try slidingSyncBuilder
.addView(v: slidingSyncView) .addView(v: slidingSyncView)
.withCommonExtensions() .withCommonExtensions()
.build() .build()
self.roomSummaryProviderInternal = RoomSummaryProvider(slidingSyncController: self.slidingSync, self.roomSummaryProvider = RoomSummaryProvider(slidingSyncController: slidingSync,
slidingSyncView: slidingSyncView, slidingSyncView: slidingSyncView,
roomMessageFactory: RoomMessageFactory()) roomMessageFactory: RoomMessageFactory())
self.slidingSync = slidingSync
} catch { } catch {
fatalError("Failed configuring sliding sync") MXLog.error("Failed configuring sliding sync with error: \(error)")
} }
} }
@@ -150,15 +146,15 @@ class ClientProxy: ClientProxyProtocol {
return return
} }
slidingSync.setObserver(observer: WeakClientProxyWrapper(clientProxy: self)) slidingSync?.setObserver(observer: WeakClientProxyWrapper(clientProxy: self))
slidingSyncObserverToken = slidingSync.sync() slidingSyncObserverToken = slidingSync?.sync()
} }
func stopSync() { func stopSync() {
client.setDelegate(delegate: nil) client.setDelegate(delegate: nil)
slidingSyncObserverToken?.cancel() slidingSyncObserverToken?.cancel()
slidingSync.setObserver(observer: nil) slidingSync?.setObserver(observer: nil)
} }
func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol? { func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol? {
@@ -252,7 +248,7 @@ class ClientProxy: ClientProxyProtocol {
private func roomTupleForIdentifier(_ identifier: String) -> (SlidingSyncRoom?, Room?) { private func roomTupleForIdentifier(_ identifier: String) -> (SlidingSyncRoom?, Room?) {
do { do {
let slidingSyncRoom = try slidingSync.getRoom(roomId: identifier) let slidingSyncRoom = try slidingSync?.getRoom(roomId: identifier)
let fullRoom = slidingSyncRoom?.fullRoom() let fullRoom = slidingSyncRoom?.fullRoom()
return (slidingSyncRoom, fullRoom) return (slidingSyncRoom, fullRoom)
@@ -271,7 +267,7 @@ class ClientProxy: ClientProxyProtocol {
} }
fileprivate func didReceiveSlidingSyncUpdate(summary: UpdateSummary) { fileprivate func didReceiveSlidingSyncUpdate(summary: UpdateSummary) {
roomSummaryProvider.updateRoomsWithIdentifiers(summary.rooms) roomSummaryProvider?.updateRoomsWithIdentifiers(summary.rooms)
callbacks.send(.receivedSyncUpdate) callbacks.send(.receivedSyncUpdate)
} }
@@ -46,7 +46,7 @@ protocol ClientProxyProtocol {
var restorationToken: RestorationToken? { get } var restorationToken: RestorationToken? { get }
var roomSummaryProvider: RoomSummaryProviderProtocol { get } var roomSummaryProvider: RoomSummaryProviderProtocol? { get }
func startSync() func startSync()
@@ -26,7 +26,7 @@ struct MockClientProxy: ClientProxyProtocol {
let homeserver = "" let homeserver = ""
let restorationToken: RestorationToken? = nil let restorationToken: RestorationToken? = nil
var roomSummaryProvider: RoomSummaryProviderProtocol = MockRoomSummaryProvider() var roomSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()
func startSync() { } func startSync() { }
+1
View File
@@ -0,0 +1 @@
Expose sliding sync proxy configuration URL on the server selection screen