From 16b2a123be74fcd6c367d4107e505e13eaa51f75 Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Fri, 19 Jan 2024 21:00:06 -0300 Subject: [PATCH] Refactor ServerListView --- ...ketChatAppRouter.swift => AppRouter.swift} | 14 +-- ios/RocketChat Watch App/AppView.swift | 32 +++--- .../Client/RocketChatClient.swift | 2 +- .../Database/RocketChatDatabase.swift | 2 +- .../Loaders/MessageSender.swift | 2 +- .../Loaders/ServersLoader.swift | 97 +++++++++++++++++++ .../Providers/ServerProvider.swift | 20 ++++ .../Providers/StateProvider.swift | 37 +++++++ ios/RocketChat Watch App/RocketChatApp.swift | 5 +- ios/RocketChat Watch App/ServerProvider.swift | 20 ---- .../ViewModels/ServerListViewModel.swift | 47 --------- .../Views/RetryView.swift | 20 ++++ .../Views/ServerListView.swift | 91 +++++++++++------ .../WatchConnection.swift | 68 ------------- ios/RocketChatRN.xcodeproj/project.pbxproj | 38 +++++--- 15 files changed, 287 insertions(+), 208 deletions(-) rename ios/RocketChat Watch App/{RocketChatAppRouter.swift => AppRouter.swift} (51%) create mode 100644 ios/RocketChat Watch App/Loaders/ServersLoader.swift create mode 100644 ios/RocketChat Watch App/Providers/ServerProvider.swift create mode 100644 ios/RocketChat Watch App/Providers/StateProvider.swift delete mode 100644 ios/RocketChat Watch App/ServerProvider.swift delete mode 100644 ios/RocketChat Watch App/ViewModels/ServerListViewModel.swift create mode 100644 ios/RocketChat Watch App/Views/RetryView.swift delete mode 100644 ios/RocketChat Watch App/WatchConnection.swift diff --git a/ios/RocketChat Watch App/RocketChatAppRouter.swift b/ios/RocketChat Watch App/AppRouter.swift similarity index 51% rename from ios/RocketChat Watch App/RocketChatAppRouter.swift rename to ios/RocketChat Watch App/AppRouter.swift index 4a2470141..3a35bc794 100644 --- a/ios/RocketChat Watch App/RocketChatAppRouter.swift +++ b/ios/RocketChat Watch App/AppRouter.swift @@ -5,25 +5,17 @@ protocol AppRouting { } final class AppRouter: ObservableObject { - @Storage(.currentServer) private var currentURL: URL? - - @Published private(set) var route: Route = .serverList + @Published private(set) var route: Route = .loading } extension AppRouter: AppRouting { func route(to route: Route) { - switch route { - case .roomList(let server): - currentURL = server.url - case .serverList: - break - } - self.route = route } } -enum Route { +enum Route: Equatable { + case loading case serverList case roomList(Server) } diff --git a/ios/RocketChat Watch App/AppView.swift b/ios/RocketChat Watch App/AppView.swift index 780c7862a..564e1cc23 100644 --- a/ios/RocketChat Watch App/AppView.swift +++ b/ios/RocketChat Watch App/AppView.swift @@ -3,8 +3,7 @@ import SwiftUI struct AppView: View { @Dependency private var database: Database @Dependency private var serversDB: ServersDatabase - - @Storage(.currentServer) private var currentURL: URL? + @Dependency private var stateProvider: StateProviding @StateObject private var router: AppRouter @@ -15,12 +14,22 @@ struct AppView: View { var body: some View { NavigationStack { switch router.route { - case .roomList(let server): - RoomListView(server: server) - .environment(\.managedObjectContext, database.viewContext) - case .serverList: - ServerListView(viewModel: ServerListViewModel()) - .environment(\.managedObjectContext, serversDB.viewContext) + case .loading: + ProgressView() + case .roomList(let server): + RoomListView(server: server) + .environment(\.managedObjectContext, database.viewContext) + case .serverList: + ServerListView() + .environment(\.managedObjectContext, serversDB.viewContext) + } + } + .onChange(of: router.route) { newValue in + switch newValue { + case .roomList(let server): + stateProvider.update(to: .loggedIn(server)) + case .serverList, .loading: + stateProvider.update(to: .loggedOut) } } .onAppear { @@ -29,11 +38,10 @@ struct AppView: View { } private func loadRoute() { - if let currentURL, let server = serversDB.server(url: currentURL) { + switch stateProvider.state { + case .loggedIn(let server): router.route(to: .roomList(server)) - } else if serversDB.servers().count == 1, let server = serversDB.servers().first { - router.route(to: .roomList(server)) - } else { + case .loggedOut: router.route(to: .serverList) } } diff --git a/ios/RocketChat Watch App/Client/RocketChatClient.swift b/ios/RocketChat Watch App/Client/RocketChatClient.swift index 1eae8a442..ef1b04e24 100644 --- a/ios/RocketChat Watch App/Client/RocketChatClient.swift +++ b/ios/RocketChat Watch App/Client/RocketChatClient.swift @@ -15,7 +15,7 @@ final class RocketChatClient: NSObject { @Dependency private var serverProvider: ServerProviding private var server: Server { - serverProvider.current() + serverProvider.server } private lazy var session = URLSession( diff --git a/ios/RocketChat Watch App/Database/RocketChatDatabase.swift b/ios/RocketChat Watch App/Database/RocketChatDatabase.swift index 5f2ac9b33..ace6f75df 100644 --- a/ios/RocketChat Watch App/Database/RocketChatDatabase.swift +++ b/ios/RocketChat Watch App/Database/RocketChatDatabase.swift @@ -29,7 +29,7 @@ final class RocketChatDatabase: Database { }() private lazy var container: NSPersistentContainer = { - let name = serverProvider.current().url.host ?? "default" + let name = serverProvider.server.url.host ?? "default" let container = NSPersistentContainer(name: name, managedObjectModel: Self.model) diff --git a/ios/RocketChat Watch App/Loaders/MessageSender.swift b/ios/RocketChat Watch App/Loaders/MessageSender.swift index 68b46a26d..6a9768dfb 100644 --- a/ios/RocketChat Watch App/Loaders/MessageSender.swift +++ b/ios/RocketChat Watch App/Loaders/MessageSender.swift @@ -15,7 +15,7 @@ extension MessageSender: MessageSending { func sendMessage(_ msg: String, in room: Room) { guard let rid = room.id else { return } - let messageID = database.createTempMessage(msg: msg, in: room, for: serverProvider.current().loggedUser) + let messageID = database.createTempMessage(msg: msg, in: room, for: serverProvider.server.loggedUser) client.sendMessage(id: messageID, rid: rid, msg: msg) .receive(on: DispatchQueue.main) diff --git a/ios/RocketChat Watch App/Loaders/ServersLoader.swift b/ios/RocketChat Watch App/Loaders/ServersLoader.swift new file mode 100644 index 000000000..a0e2df818 --- /dev/null +++ b/ios/RocketChat Watch App/Loaders/ServersLoader.swift @@ -0,0 +1,97 @@ +import Combine +import Foundation +import WatchConnectivity + +enum ServersLoadingError: Error, Equatable { + case unactive + case unreachable + case locked + case undecodable(Error) + + static func == (lhs: ServersLoadingError, rhs: ServersLoadingError) -> Bool { + switch (lhs, rhs) { + case (.unactive, .unactive), (.unreachable, .unreachable), (.locked, .locked), (.undecodable, .undecodable): + return true + default: + return false + } + } +} + +protocol ServersLoading { + func loadServers() -> AnyPublisher +} + +final class ServersLoader: NSObject { + @Dependency private var database: ServersDatabase + + private let session: WCSession + + init(session: WCSession) { + self.session = session + super.init() + session.delegate = self + session.activate() + } + + private func sendMessage(completionHandler: @escaping (Result) -> Void) { + print("sendMessage") + + guard session.activationState == .activated else { + completionHandler(.failure(.unactive)) + return + } + + guard !session.iOSDeviceNeedsUnlockAfterRebootForReachability else { + completionHandler(.failure(.locked)) + return + } + + guard session.isReachable else { + completionHandler(.failure(.unreachable)) + return + } + + session.sendMessage([:]) { dictionary in + do { + let data = try JSONSerialization.data(withJSONObject: dictionary) + let message = try JSONDecoder().decode(WatchMessage.self, from: data) + + completionHandler(.success(message)) + } catch { + completionHandler(.failure(.undecodable(error))) + } + } + } +} + +// MARK: - WCSessionDelegate + +extension ServersLoader: WCSessionDelegate { + func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { + + } +} + +// MARK: - ServersLoading + +extension ServersLoader: ServersLoading { + func loadServers() -> AnyPublisher { + Future { [self] promise in + sendMessage { result in + switch result { + case .success(let message): + for server in message.servers { + self.database.process(updatedServer: server) + } + + promise(.success(())) + case .failure(let error): + promise(.failure(error)) + } + } + } + .retryWithDelay(retries: 3, delay: 1, scheduler: DispatchQueue.global()) + .eraseToAnyPublisher() + } +} diff --git a/ios/RocketChat Watch App/Providers/ServerProvider.swift b/ios/RocketChat Watch App/Providers/ServerProvider.swift new file mode 100644 index 000000000..cf76e4632 --- /dev/null +++ b/ios/RocketChat Watch App/Providers/ServerProvider.swift @@ -0,0 +1,20 @@ +import Foundation + +protocol ServerProviding { + var server: Server { get } +} + +final class ServerProvider { + @Dependency private var stateProvider: StateProviding +} + +extension ServerProvider: ServerProviding { + var server: Server { + switch stateProvider.state { + case .loggedIn(let server): + return server + case .loggedOut: + fatalError("Attempt to get server while logged out.") + } + } +} diff --git a/ios/RocketChat Watch App/Providers/StateProvider.swift b/ios/RocketChat Watch App/Providers/StateProvider.swift new file mode 100644 index 000000000..6f4cda320 --- /dev/null +++ b/ios/RocketChat Watch App/Providers/StateProvider.swift @@ -0,0 +1,37 @@ +import Foundation + +enum AppState { + case loggedIn(Server) + case loggedOut +} + +protocol StateProviding { + var state: AppState { get } + + func update(to state: AppState) +} + +final class StateProvider: StateProviding { + @Storage(.currentServer) private var currentURL: URL? + + @Dependency private var database: ServersDatabase + + var state: AppState { + if let currentURL, let server = database.server(url: currentURL) { + return .loggedIn(server) + } else if database.servers().count == 1, let server = database.servers().first { + return .loggedIn(server) + } else { + return .loggedOut + } + } + + func update(to state: AppState) { + switch state { + case .loggedIn(let server): + currentURL = server.url + case .loggedOut: + break + } + } +} diff --git a/ios/RocketChat Watch App/RocketChatApp.swift b/ios/RocketChat Watch App/RocketChatApp.swift index fa1cdc67a..b752cae4c 100644 --- a/ios/RocketChat Watch App/RocketChatApp.swift +++ b/ios/RocketChat Watch App/RocketChatApp.swift @@ -9,15 +9,16 @@ struct RocketChat_Watch_AppApp: App { } private func registerDependencies() { + Store.register(AppRouting.self, factory: router) Store.register(ServersDatabase.self, factory: DefaultDatabase()) + Store.register(StateProviding.self, factory: StateProvider()) Store.register(ServerProviding.self, factory: ServerProvider()) - Store.register(Connection.self, factory: WatchConnection(session: .default)) + Store.register(ServersLoading.self, factory: ServersLoader(session: .default)) Store.register(RocketChatClientProtocol.self, factory: RocketChatClient()) Store.register(Database.self, factory: RocketChatDatabase()) Store.register(MessagesLoading.self, factory: MessagesLoader()) Store.register(MessageSending.self, factory: MessageSender()) Store.register(RoomsLoading.self, factory: RoomsLoader()) - Store.register(AppRouting.self, factory: router) } var body: some Scene { diff --git a/ios/RocketChat Watch App/ServerProvider.swift b/ios/RocketChat Watch App/ServerProvider.swift deleted file mode 100644 index d2e56a8d4..000000000 --- a/ios/RocketChat Watch App/ServerProvider.swift +++ /dev/null @@ -1,20 +0,0 @@ -import Foundation - -protocol ServerProviding { - func current() -> Server -} - -final class ServerProvider { - @Storage(.currentServer) private var currentURL: URL? - @Dependency private var database: ServersDatabase -} - -extension ServerProvider: ServerProviding { - func current() -> Server { - if let currentURL, let server = database.server(url: currentURL) { - return server - } else { - fatalError("Attempt to get server before it was not set.") - } - } -} diff --git a/ios/RocketChat Watch App/ViewModels/ServerListViewModel.swift b/ios/RocketChat Watch App/ViewModels/ServerListViewModel.swift deleted file mode 100644 index 7b4eb4ad4..000000000 --- a/ios/RocketChat Watch App/ViewModels/ServerListViewModel.swift +++ /dev/null @@ -1,47 +0,0 @@ -import Foundation - -enum ServerListState { - case loading - case loaded - case error(ConnectionError) -} - -final class ServerListViewModel: ObservableObject { - @Dependency private var connection: Connection - @Dependency private var database: ServersDatabase - @Dependency private var router: AppRouting - - @Published private(set) var state: ServerListState = .loading - - private func handleSuccess(message: WatchMessage) { - message.servers.forEach(database.process(updatedServer:)) - state = .loaded - } - - private func handleFailure(error: Error) { - guard let connectionError = error as? ConnectionError else { - return - } - - state = .error(connectionError) - } - - func loadServers() { - connection.sendMessage { [weak self] result in - guard let self else { - return - } - - switch result { - case .success(let message): - DispatchQueue.main.async { self.handleSuccess(message: message) } - case .failure(let error): - DispatchQueue.main.async { self.handleFailure(error: error) } - } - } - } - - func didTap(server: Server) { - router.route(to: .roomList(server)) - } -} diff --git a/ios/RocketChat Watch App/Views/RetryView.swift b/ios/RocketChat Watch App/Views/RetryView.swift new file mode 100644 index 000000000..59d26ce8e --- /dev/null +++ b/ios/RocketChat Watch App/Views/RetryView.swift @@ -0,0 +1,20 @@ +import SwiftUI + +struct RetryView: View { + private let label: String + private let action: () -> Void + + init(_ label: String, action: @escaping () -> Void) { + self.label = label + self.action = action + } + + var body: some View { + VStack { + Text(label) + .multilineTextAlignment(.center) + Button("Try Again", action: action) + } + .padding() + } +} diff --git a/ios/RocketChat Watch App/Views/ServerListView.swift b/ios/RocketChat Watch App/Views/ServerListView.swift index b2b9b7cd3..7780e7198 100644 --- a/ios/RocketChat Watch App/Views/ServerListView.swift +++ b/ios/RocketChat Watch App/Views/ServerListView.swift @@ -1,53 +1,80 @@ +import Combine +import CoreData import SwiftUI struct ServerListView: View { - @StateObject var viewModel: ServerListViewModel + @Dependency private var router: AppRouting + @Dependency private var serversLoader: ServersLoading - @FetchRequest(entity: Server.entity(), sortDescriptors: [], animation: .default) - private var servers: FetchedResults + @State private var state: ViewState = .loading - init(viewModel: ServerListViewModel) { - _viewModel = StateObject(wrappedValue: viewModel) + @FetchRequest private var servers: FetchedResults + + init() { + let fetchRequest = Server.fetchRequest() + fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Server.updatedSince, ascending: true)] + + _servers = FetchRequest(fetchRequest: fetchRequest) } @ViewBuilder - private func errorView(_ text: String) -> some View { - VStack(alignment: .center) { - Text(text) - Button("Try Again") { - viewModel.loadServers() + private var serverList: some View { + List { + ForEach(servers) { server in + ServerView(server: server) + .onTapGesture { + router.route(to: .roomList(server)) + } } } } var body: some View { VStack { - switch viewModel.state { - case .loading: - ProgressView() - case .loaded where servers.count > 0: - List { - ForEach(servers) { server in - ServerView(server: server) - .onTapGesture { - viewModel.didTap(server: server) - } - } - } - case .loaded: - errorView("There are no servers connected.") - case .error(let connectionError): - switch connectionError { - case .needsUnlock: - errorView("You need to unlock your iPhone.") - case .decoding: - errorView("We can't read servers information.") - } + switch state { + case .loading: + ProgressView() + case .loaded: + serverList + case .loaded where servers.isEmpty: + RetryView("No Connected servers.", action: loadServers) + case .error(let error) where error == .locked: + RetryView("Please unlock your iPhone.", action: loadServers) + case .error(let error) where error == .unactive: + RetryView("Could not connect to your iPhone.", action: loadServers) + case .error(let error) where error == .unreachable: + RetryView("Could not reach your iPhone.", action: loadServers) + case .error(let error) where error == .undecodable(error): + RetryView("Could not read servers from iPhone.", action: loadServers) + default: + RetryView("Unexpected error.", action: loadServers) } } .navigationTitle("Servers") .onAppear { - viewModel.loadServers() + loadServers() } } + + private func loadServers() { + state = .loading + + serversLoader.loadServers() + .receive(on: DispatchQueue.main) + .subscribe(Subscribers.Sink { completion in + if case .failure(let error) = completion { + state = .error(error) + } + } receiveValue: { _ in + state = .loaded + }) + } +} + +extension ServerListView { + enum ViewState { + case loading + case loaded + case error(ServersLoadingError) + } } diff --git a/ios/RocketChat Watch App/WatchConnection.swift b/ios/RocketChat Watch App/WatchConnection.swift deleted file mode 100644 index 4007636c3..000000000 --- a/ios/RocketChat Watch App/WatchConnection.swift +++ /dev/null @@ -1,68 +0,0 @@ -import Foundation -import WatchConnectivity - -enum ConnectionError: Error { - case needsUnlock - case decoding(Error) -} - -protocol Connection { - func sendMessage(completionHandler: @escaping (Result) -> Void) -} - -final class WatchConnection: NSObject { - private let session: WCSession - - init(session: WCSession) { - self.session = session - super.init() - session.delegate = self - session.activate() - } - - private func scheduledSendMessage(completionHandler: @escaping (Result) -> Void) { - Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { [weak self] _ in - self?.sendMessage(completionHandler: completionHandler) - } - } -} - -// MARK: - WCSessionDelegate - -extension WatchConnection: WCSessionDelegate { - func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { - - } -} - -// MARK: - Connection - -extension WatchConnection: Connection { - func sendMessage(completionHandler: @escaping (Result) -> Void) { - guard session.activationState == .activated else { - scheduledSendMessage(completionHandler: completionHandler) - return - } - - guard !session.iOSDeviceNeedsUnlockAfterRebootForReachability else { - completionHandler(.failure(.needsUnlock)) - return - } - - guard session.isReachable else { - scheduledSendMessage(completionHandler: completionHandler) - return - } - - session.sendMessage([:]) { dictionary in - do { - let data = try JSONSerialization.data(withJSONObject: dictionary) - let message = try JSONDecoder().decode(WatchMessage.self, from: data) - - completionHandler(.success(message)) - } catch { - completionHandler(.failure(.decoding(error))) - } - } - } -} diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index 8a9fa0b22..9b230753e 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -82,6 +82,8 @@ 1E4AFC1B2B5AFC6A00E2AA7D /* Publisher+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E4AFC1A2B5AFC6A00E2AA7D /* Publisher+Extensions.swift */; }; 1E4AFC1F2B5B0D0500E2AA7D /* ServerProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E4AFC1E2B5B0D0500E2AA7D /* ServerProvider.swift */; }; 1E4AFC212B5B1AA000E2AA7D /* AppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E4AFC202B5B1AA000E2AA7D /* AppView.swift */; }; + 1E4AFC252B5B1DA300E2AA7D /* StateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E4AFC242B5B1DA300E2AA7D /* StateProvider.swift */; }; + 1E4AFC272B5B23C600E2AA7D /* RetryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E4AFC262B5B23C600E2AA7D /* RetryView.swift */; }; 1E51D962251263CD00DC95DE /* MessageType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E51D961251263CD00DC95DE /* MessageType.swift */; }; 1E51D965251263D600DC95DE /* NotificationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E51D964251263D600DC95DE /* NotificationType.swift */; }; 1E598AE42515057D002BDFBD /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E598AE32515057D002BDFBD /* Date+Extensions.swift */; }; @@ -127,11 +129,10 @@ 1ED033AE2B55B1CC004F4930 /* Default.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 1ED033AC2B55B1CC004F4930 /* Default.xcdatamodeld */; }; 1ED033B02B55B25A004F4930 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED033AF2B55B25A004F4930 /* Database.swift */; }; 1ED033B62B55B4A5004F4930 /* ServerListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED033B52B55B4A5004F4930 /* ServerListView.swift */; }; - 1ED033B82B55B4BE004F4930 /* ServerListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED033B72B55B4BE004F4930 /* ServerListViewModel.swift */; }; 1ED033BA2B55B5F6004F4930 /* ServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED033B92B55B5F6004F4930 /* ServerView.swift */; }; 1ED033BF2B55BF94004F4930 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED033BE2B55BF94004F4930 /* Storage.swift */; }; 1ED033C12B55C190004F4930 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 1ED033C02B55C190004F4930 /* Localizable.xcstrings */; }; - 1ED033C42B55C65C004F4930 /* RocketChatAppRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED033C32B55C65C004F4930 /* RocketChatAppRouter.swift */; }; + 1ED033C42B55C65C004F4930 /* AppRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED033C32B55C65C004F4930 /* AppRouter.swift */; }; 1ED033CB2B55D4F0004F4930 /* RocketChat.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 1ED033C92B55D4F0004F4930 /* RocketChat.xcdatamodeld */; }; 1ED033CD2B55D671004F4930 /* RocketChatDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED033CC2B55D671004F4930 /* RocketChatDatabase.swift */; }; 1ED038912B507B4C00C007D4 /* RocketChatApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038902B507B4C00C007D4 /* RocketChatApp.swift */; }; @@ -159,7 +160,7 @@ 1ED038C42B50A1F500C007D4 /* WatchMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038C32B50A1F500C007D4 /* WatchMessage.swift */; }; 1ED038C52B50A1F500C007D4 /* WatchMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038C32B50A1F500C007D4 /* WatchMessage.swift */; }; 1ED038C62B50A21800C007D4 /* WatchMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038C32B50A1F500C007D4 /* WatchMessage.swift */; }; - 1ED038CA2B50A58400C007D4 /* WatchConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038C92B50A58400C007D4 /* WatchConnection.swift */; }; + 1ED038CA2B50A58400C007D4 /* ServersLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038C92B50A58400C007D4 /* ServersLoader.swift */; }; 1ED59D4C22CBA77D00C54289 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 1ED59D4B22CBA77D00C54289 /* GoogleService-Info.plist */; }; 1EDFD0FA2B589B8F002FEE5F /* MessagesLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EDFD0F92B589B8F002FEE5F /* MessagesLoader.swift */; }; 1EDFD1062B58A66E002FEE5F /* CancelBag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EDFD1052B58A66E002FEE5F /* CancelBag.swift */; }; @@ -394,6 +395,8 @@ 1E4AFC1A2B5AFC6A00E2AA7D /* Publisher+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+Extensions.swift"; sourceTree = ""; }; 1E4AFC1E2B5B0D0500E2AA7D /* ServerProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerProvider.swift; sourceTree = ""; }; 1E4AFC202B5B1AA000E2AA7D /* AppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppView.swift; sourceTree = ""; }; + 1E4AFC242B5B1DA300E2AA7D /* StateProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateProvider.swift; sourceTree = ""; }; + 1E4AFC262B5B23C600E2AA7D /* RetryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetryView.swift; sourceTree = ""; }; 1E51D961251263CD00DC95DE /* MessageType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageType.swift; sourceTree = ""; }; 1E51D964251263D600DC95DE /* NotificationType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationType.swift; sourceTree = ""; }; 1E598AE32515057D002BDFBD /* Date+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = ""; }; @@ -418,11 +421,10 @@ 1ED033AD2B55B1CC004F4930 /* Default.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Default.xcdatamodel; sourceTree = ""; }; 1ED033AF2B55B25A004F4930 /* Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Database.swift; sourceTree = ""; }; 1ED033B52B55B4A5004F4930 /* ServerListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListView.swift; sourceTree = ""; }; - 1ED033B72B55B4BE004F4930 /* ServerListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListViewModel.swift; sourceTree = ""; }; 1ED033B92B55B5F6004F4930 /* ServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerView.swift; sourceTree = ""; }; 1ED033BE2B55BF94004F4930 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = ""; }; 1ED033C02B55C190004F4930 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; - 1ED033C32B55C65C004F4930 /* RocketChatAppRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RocketChatAppRouter.swift; sourceTree = ""; }; + 1ED033C32B55C65C004F4930 /* AppRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouter.swift; sourceTree = ""; }; 1ED033CA2B55D4F0004F4930 /* RocketChat.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = RocketChat.xcdatamodel; sourceTree = ""; }; 1ED033CC2B55D671004F4930 /* RocketChatDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RocketChatDatabase.swift; sourceTree = ""; }; 1ED0388E2B507B4B00C007D4 /* Rocket.Chat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Rocket.Chat.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -437,7 +439,7 @@ 1ED038BD2B50A1D400C007D4 /* DBServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBServer.swift; sourceTree = ""; }; 1ED038C02B50A1E400C007D4 /* DBUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBUser.swift; sourceTree = ""; }; 1ED038C32B50A1F500C007D4 /* WatchMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchMessage.swift; sourceTree = ""; }; - 1ED038C92B50A58400C007D4 /* WatchConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchConnection.swift; sourceTree = ""; }; + 1ED038C92B50A58400C007D4 /* ServersLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServersLoader.swift; sourceTree = ""; }; 1ED59D4B22CBA77D00C54289 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = SOURCE_ROOT; }; 1EDFD0F92B589B8F002FEE5F /* MessagesLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesLoader.swift; sourceTree = ""; }; 1EDFD1052B58A66E002FEE5F /* CancelBag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelBag.swift; sourceTree = ""; }; @@ -690,6 +692,15 @@ path = Requests; sourceTree = ""; }; + 1E4AFC232B5B1D9C00E2AA7D /* Providers */ = { + isa = PBXGroup; + children = ( + 1E4AFC1E2B5B0D0500E2AA7D /* ServerProvider.swift */, + 1E4AFC242B5B1DA300E2AA7D /* StateProvider.swift */, + ); + path = Providers; + sourceTree = ""; + }; 1E76CBC425152A7F0067298C /* Shared */ = { isa = PBXGroup; children = ( @@ -790,6 +801,7 @@ 1E29A3172B5868E50093C03C /* MessageView.swift */, 1E29A3232B5874FF0093C03C /* MessageComposerView.swift */, 1E9A716E2B59CBCA00477BA2 /* AttachmentView.swift */, + 1E4AFC262B5B23C600E2AA7D /* RetryView.swift */, ); path = Views; sourceTree = ""; @@ -797,7 +809,6 @@ 1ED033B42B55B495004F4930 /* ViewModels */ = { isa = PBXGroup; children = ( - 1ED033B72B55B4BE004F4930 /* ServerListViewModel.swift */, 1E29A30F2B5865B80093C03C /* RoomViewModel.swift */, 1E29A3192B5868EE0093C03C /* MessageViewModel.swift */, ); @@ -807,6 +818,7 @@ 1ED0388F2B507B4C00C007D4 /* RocketChat Watch App */ = { isa = PBXGroup; children = ( + 1E4AFC232B5B1D9C00E2AA7D /* Providers */, 1EDFD0FB2B589FC4002FEE5F /* DependencyInjection */, 1EDFD0F82B589B82002FEE5F /* Loaders */, 1E29A31E2B5871BE0093C03C /* Formatters */, @@ -816,14 +828,12 @@ 1ED033B12B55B47F004F4930 /* Views */, 1ED033AB2B55B1C2004F4930 /* Database */, 1ED038902B507B4C00C007D4 /* RocketChatApp.swift */, - 1ED033C32B55C65C004F4930 /* RocketChatAppRouter.swift */, + 1ED033C32B55C65C004F4930 /* AppRouter.swift */, 1E4AFC202B5B1AA000E2AA7D /* AppView.swift */, - 1ED038C92B50A58400C007D4 /* WatchConnection.swift */, 1ED033BE2B55BF94004F4930 /* Storage.swift */, 1ED038942B507B4D00C007D4 /* Assets.xcassets */, 1ED038962B507B4D00C007D4 /* Preview Content */, 1ED033C02B55C190004F4930 /* Localizable.xcstrings */, - 1E4AFC1E2B5B0D0500E2AA7D /* ServerProvider.swift */, ); path = "RocketChat Watch App"; sourceTree = ""; @@ -858,6 +868,7 @@ 1EDFD0F82B589B82002FEE5F /* Loaders */ = { isa = PBXGroup; children = ( + 1ED038C92B50A58400C007D4 /* ServersLoader.swift */, 1EDFD0F92B589B8F002FEE5F /* MessagesLoader.swift */, 1EDFD1052B58A66E002FEE5F /* CancelBag.swift */, 1EDFD1072B58AA77002FEE5F /* RoomsLoader.swift */, @@ -1858,12 +1869,13 @@ 1ED033BA2B55B5F6004F4930 /* ServerView.swift in Sources */, 1E29A2D02B58582F0093C03C /* RoomView.swift in Sources */, 1E29A2FD2B585B070093C03C /* RoomsRequest.swift in Sources */, - 1ED038CA2B50A58400C007D4 /* WatchConnection.swift in Sources */, + 1ED038CA2B50A58400C007D4 /* ServersLoader.swift in Sources */, 1E29A3222B5871CE0093C03C /* MessageFormatter.swift in Sources */, 1E9A716F2B59CBCA00477BA2 /* AttachmentView.swift in Sources */, 1E29A3002B585B070093C03C /* JSONAdapter.swift in Sources */, 1E29A3022B585B070093C03C /* DateCodingStrategy.swift in Sources */, 1E4AFC1B2B5AFC6A00E2AA7D /* Publisher+Extensions.swift in Sources */, + 1E4AFC272B5B23C600E2AA7D /* RetryView.swift in Sources */, 1ED033B62B55B4A5004F4930 /* ServerListView.swift in Sources */, 1E29A3202B5871C80093C03C /* RoomFormatter.swift in Sources */, 1EDFD0FA2B589B8F002FEE5F /* MessagesLoader.swift in Sources */, @@ -1903,8 +1915,8 @@ 1ED038912B507B4C00C007D4 /* RocketChatApp.swift in Sources */, 1E29A2CC2B5857F50093C03C /* RoomListView.swift in Sources */, 1E29A31A2B5868EE0093C03C /* MessageViewModel.swift in Sources */, - 1ED033B82B55B4BE004F4930 /* ServerListViewModel.swift in Sources */, - 1ED033C42B55C65C004F4930 /* RocketChatAppRouter.swift in Sources */, + 1ED033C42B55C65C004F4930 /* AppRouter.swift in Sources */, + 1E4AFC252B5B1DA300E2AA7D /* StateProvider.swift in Sources */, 1ED033B02B55B25A004F4930 /* Database.swift in Sources */, 1E9A71712B59CC1300477BA2 /* Attachment.swift in Sources */, 1E29A30A2B585B370093C03C /* Data+Extensions.swift in Sources */,