From 797076bb152df8f8697f4239608283f9fc3b1880 Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Sat, 23 Mar 2024 07:29:35 -0300 Subject: [PATCH] New navigation flow --- ios/RocketChat Watch App/AppRouter.swift | 41 ++++++++++++++++--- ios/RocketChat Watch App/AppView.swift | 14 ++----- ios/RocketChat Watch App/Deeplink.swift | 7 +++- .../Extensions/Binding+Extensions.swift | 24 +++++++++++ .../Loaders/ServersLoader.swift | 4 +- ios/RocketChat Watch App/Views/LazyView.swift | 13 ++++++ .../Views/LoggedInView.swift | 21 +++------- .../Views/NavigationCompatibleView.swift | 21 ---------- .../Views/NavigationStackModifier.swift | 30 ++++++++++++++ .../Views/RoomListView.swift | 29 ++++++------- .../Views/ServerListView.swift | 9 ++-- ios/RocketChatRN.xcodeproj/project.pbxproj | 24 ++++++++--- 12 files changed, 158 insertions(+), 79 deletions(-) create mode 100644 ios/RocketChat Watch App/Extensions/Binding+Extensions.swift create mode 100644 ios/RocketChat Watch App/Views/LazyView.swift delete mode 100644 ios/RocketChat Watch App/Views/NavigationCompatibleView.swift create mode 100644 ios/RocketChat Watch App/Views/NavigationStackModifier.swift diff --git a/ios/RocketChat Watch App/AppRouter.swift b/ios/RocketChat Watch App/AppRouter.swift index 6f1b3eac2..797ec24e0 100644 --- a/ios/RocketChat Watch App/AppRouter.swift +++ b/ios/RocketChat Watch App/AppRouter.swift @@ -10,21 +10,49 @@ final class AppRouter: ObservableObject { @Published var error: ErrorResponse? + @Published var server: Server? { + didSet { + if server != oldValue, let server { + registerDependencies(in: server) + } + } + } + + @Published var room: Room? + @Storage(.currentServer) private var currentURL: URL? + + private func registerDependencies(in server: Server) { + Store.register(Database.self, factory: RocketChatDatabase(server: server)) + Store.register(RocketChatClientProtocol.self, factory: RocketChatClient(server: server)) + Store.register(MessageSending.self, factory: MessageSender(server: server)) + Store.register(ErrorActionHandling.self, factory: ErrorActionHandler(server: server)) + Store.register(MessagesLoading.self, factory: MessagesLoader()) + Store.register(RoomsLoader.self, factory: RoomsLoader(server: server)) + } } extension AppRouter: AppRouting { func route(to route: Route) { - self.route = route - switch route { - case .roomList(let server): - currentURL = server.url + case .roomList(let selectedServer): + currentURL = selectedServer.url + room = nil + server = selectedServer + case .room(let selectedServer, let selectedRoom): + currentURL = selectedServer.url + server = selectedServer + room = selectedRoom case .serverList: currentURL = nil - default: - break + room = nil + server = nil + case .loading: + room = nil + server = nil } + + self.route = route } func present(error: ErrorResponse) { @@ -40,4 +68,5 @@ enum Route: Equatable { case loading case serverList case roomList(Server) + case room(Server, Room) } diff --git a/ios/RocketChat Watch App/AppView.swift b/ios/RocketChat Watch App/AppView.swift index ea5200411..c95569631 100644 --- a/ios/RocketChat Watch App/AppView.swift +++ b/ios/RocketChat Watch App/AppView.swift @@ -12,16 +12,10 @@ struct AppView: View { } var body: some View { - NavigationCompatibleView { - switch router.route { - case .loading: - ProgressView() - case .roomList(let server): - LoggedInView(server: server) - case .serverList: - ServerListView() - .environment(\.managedObjectContext, database.viewContext) - } + NavigationView { + ServerListView() + .environmentObject(router) + .environment(\.managedObjectContext, database.viewContext) } .onAppear { loadRoute() diff --git a/ios/RocketChat Watch App/Deeplink.swift b/ios/RocketChat Watch App/Deeplink.swift index 5e917aa47..eacc79d75 100644 --- a/ios/RocketChat Watch App/Deeplink.swift +++ b/ios/RocketChat Watch App/Deeplink.swift @@ -18,6 +18,11 @@ final class Deeplink: Deeplinking { return } - router.route(to: .roomList(server)) + guard let room = RocketChatDatabase(server: server).room(id: response.rid) else { + return + } + + router.route(to: .room(server, room)) + holder.clear() } } diff --git a/ios/RocketChat Watch App/Extensions/Binding+Extensions.swift b/ios/RocketChat Watch App/Extensions/Binding+Extensions.swift new file mode 100644 index 000000000..7116086ca --- /dev/null +++ b/ios/RocketChat Watch App/Extensions/Binding+Extensions.swift @@ -0,0 +1,24 @@ +import SwiftUI + +extension Binding where Value == Bool { + init(bindingOptional: Binding) { + self.init( + get: { + bindingOptional.wrappedValue != nil + }, + set: { newValue in + guard newValue == false else { return } + + /// We only handle `false` booleans to set our optional to `nil` + /// as we can't handle `true` for restoring the previous value. + bindingOptional.wrappedValue = nil + } + ) + } +} + +extension Binding { + func mappedToBool() -> Binding where Value == Wrapped? { + Binding(bindingOptional: self) + } +} diff --git a/ios/RocketChat Watch App/Loaders/ServersLoader.swift b/ios/RocketChat Watch App/Loaders/ServersLoader.swift index e82088705..fb6708aba 100644 --- a/ios/RocketChat Watch App/Loaders/ServersLoader.swift +++ b/ios/RocketChat Watch App/Loaders/ServersLoader.swift @@ -42,7 +42,9 @@ extension ServersLoader: ServersLoading { switch result { case .success(let message): for server in message.servers { - self.database.process(updatedServer: server) + DispatchQueue.main.async { + self.database.process(updatedServer: server) + } } promise(.success(())) diff --git a/ios/RocketChat Watch App/Views/LazyView.swift b/ios/RocketChat Watch App/Views/LazyView.swift new file mode 100644 index 000000000..ecd6fac0b --- /dev/null +++ b/ios/RocketChat Watch App/Views/LazyView.swift @@ -0,0 +1,13 @@ +import SwiftUI + +struct LazyView: View { + private let build: () -> Content + + init(_ build: @autoclosure @escaping () -> Content) { + self.build = build + } + + var body: Content { + build() + } +} diff --git a/ios/RocketChat Watch App/Views/LoggedInView.swift b/ios/RocketChat Watch App/Views/LoggedInView.swift index a71c6e98a..6d507a1be 100644 --- a/ios/RocketChat Watch App/Views/LoggedInView.swift +++ b/ios/RocketChat Watch App/Views/LoggedInView.swift @@ -1,29 +1,20 @@ import SwiftUI struct LoggedInView: View { - @Dependency private var router: AppRouting + @Dependency private var database: Database + @Dependency private var roomsLoader: RoomsLoader + + @EnvironmentObject private var router: AppRouter - private let database: Database private let server: Server init(server: Server) { self.server = server - self.database = RocketChatDatabase(server: server) - - registerDependencies() - } - - private func registerDependencies() { - Store.register(Database.self, factory: database) - Store.register(RocketChatClientProtocol.self, factory: RocketChatClient(server: server)) - Store.register(MessageSending.self, factory: MessageSender(server: server)) - Store.register(ErrorActionHandling.self, factory: ErrorActionHandler(server: server)) - Store.register(MessagesLoading.self, factory: MessagesLoader()) } var body: some View { - RoomListView(server: server) - .environmentObject(RoomsLoader(server: server)) + RoomListView(server: server, roomsLoader: roomsLoader) + .environmentObject(router) .environment(\.managedObjectContext, database.viewContext) } } diff --git a/ios/RocketChat Watch App/Views/NavigationCompatibleView.swift b/ios/RocketChat Watch App/Views/NavigationCompatibleView.swift deleted file mode 100644 index 7fe57ddeb..000000000 --- a/ios/RocketChat Watch App/Views/NavigationCompatibleView.swift +++ /dev/null @@ -1,21 +0,0 @@ -import SwiftUI - -struct NavigationCompatibleView: View { - private let content: () -> Content - - init(@ViewBuilder content: @escaping () -> Content) { - self.content = content - } - - var body: some View { - if #available(watchOS 10.0, *) { - NavigationStack { - content() - } - } else { - NavigationView { - content() - } - } - } -} diff --git a/ios/RocketChat Watch App/Views/NavigationStackModifier.swift b/ios/RocketChat Watch App/Views/NavigationStackModifier.swift new file mode 100644 index 000000000..a6dbecfe5 --- /dev/null +++ b/ios/RocketChat Watch App/Views/NavigationStackModifier.swift @@ -0,0 +1,30 @@ +import SwiftUI + +struct NavigationStackModifier: ViewModifier { + let item: Binding + let destination: (Item) -> Destination + + func body(content: Content) -> some View { + content.background { + NavigationLink(isActive: item.mappedToBool()) { + if let item = item.wrappedValue { + destination(item) + } else { + EmptyView() + } + } label: { + EmptyView() + } + .opacity(0) + } + } +} + +public extension View { + func navigationDestination( + for binding: Binding, + @ViewBuilder destination: @escaping (Item) -> Destination + ) -> some View { + self.modifier(NavigationStackModifier(item: binding, destination: destination)) + } +} diff --git a/ios/RocketChat Watch App/Views/RoomListView.swift b/ios/RocketChat Watch App/Views/RoomListView.swift index 67c9ec107..bc90f600e 100644 --- a/ios/RocketChat Watch App/Views/RoomListView.swift +++ b/ios/RocketChat Watch App/Views/RoomListView.swift @@ -2,11 +2,12 @@ import SwiftUI struct RoomListView: View { @Dependency private var database: Database - @Dependency private var router: AppRouting + + @EnvironmentObject private var router: AppRouter @ObservedObject private var server: Server - @EnvironmentObject private var roomsLoader: RoomsLoader + @StateObject private var roomsLoader: RoomsLoader @Environment(\.scenePhase) private var scenePhase @@ -14,19 +15,22 @@ struct RoomListView: View { @State private var roomID: String? - init(server: Server) { + init(server: Server, roomsLoader: RoomsLoader) { self.server = server + _roomsLoader = StateObject(wrappedValue: roomsLoader) _rooms = FetchRequest(fetchRequest: server.roomsRequest) } var body: some View { List(rooms, id: \.id) { room in - NavigationLink(tag: room.rid, selection: $roomID) { - MessageListView(room: room, server: server) - .environment(\.managedObjectContext, database.viewContext) - } label: { - RoomView(viewModel: .init(room: room, server: server)) - } + RoomView(viewModel: .init(room: room, server: server)) + .onTapGesture { + router.route(to: .room(server, room)) + } + } + .navigationDestination(for: $router.room) { room in + MessageListView(room: room, server: server) + .environment(\.managedObjectContext, database.viewContext) } .onAppear { roomsLoader.start() @@ -45,13 +49,6 @@ struct RoomListView: View { } } .navigationTitle("Rooms") - .toolbar { - ToolbarItem(placement: .automatic) { - Button("Servers") { - router.route(to: .serverList) - } - } - } .overlay { if roomsLoader.state == .loading { ProgressView() diff --git a/ios/RocketChat Watch App/Views/ServerListView.swift b/ios/RocketChat Watch App/Views/ServerListView.swift index 23e6fc9ab..0b72ba42a 100644 --- a/ios/RocketChat Watch App/Views/ServerListView.swift +++ b/ios/RocketChat Watch App/Views/ServerListView.swift @@ -3,15 +3,14 @@ import CoreData import SwiftUI struct ServerListView: View { - @Dependency private var router: AppRouting + @EnvironmentObject private var router: AppRouter + @Dependency private var serversLoader: ServersLoading @State private var state: ViewState = .loading @FetchRequest private var servers: FetchedResults - @Environment(\.scenePhase) private var scenePhase - init() { let fetchRequest = Server.fetchRequest() fetchRequest.sortDescriptors = [] @@ -63,6 +62,10 @@ struct ServerListView: View { Text("Servers").foregroundColor(.red) } .navigationBarTitleDisplayMode(.inline) + .navigationDestination(for: $router.server) { server in + LoggedInView(server: server) + .environmentObject(router) + } .toolbar { ToolbarItem(placement: .default) { Button { diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index b00e5417f..3ad6c1f32 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -257,10 +257,14 @@ 1EDFD0FA2B589B8F002FEE5F /* MessagesLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EDFD0F92B589B8F002FEE5F /* MessagesLoader.swift */; }; 1EDFD1062B58A66E002FEE5F /* CancelBag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EDFD1052B58A66E002FEE5F /* CancelBag.swift */; }; 1EDFD1082B58AA77002FEE5F /* RoomsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EDFD1072B58AA77002FEE5F /* RoomsLoader.swift */; }; - 1EE096F72BACD1E400780078 /* NavigationCompatibleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EE096F62BACD1E400780078 /* NavigationCompatibleView.swift */; }; - 1EE096F82BACD1E400780078 /* NavigationCompatibleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EE096F62BACD1E400780078 /* NavigationCompatibleView.swift */; }; 1EE096FA2BACD1F200780078 /* ToolbarItemPlacement+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EE096F92BACD1F200780078 /* ToolbarItemPlacement+Extensions.swift */; }; 1EE096FB2BACD1F200780078 /* ToolbarItemPlacement+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EE096F92BACD1F200780078 /* ToolbarItemPlacement+Extensions.swift */; }; + 1EE096FD2BACD58300780078 /* LazyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EE096FC2BACD58300780078 /* LazyView.swift */; }; + 1EE096FE2BACD58300780078 /* LazyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EE096FC2BACD58300780078 /* LazyView.swift */; }; + 1EE097002BACD64C00780078 /* Binding+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EE096FF2BACD64C00780078 /* Binding+Extensions.swift */; }; + 1EE097012BACD64C00780078 /* Binding+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EE096FF2BACD64C00780078 /* Binding+Extensions.swift */; }; + 1EE097032BACD66900780078 /* NavigationStackModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EE097022BACD66900780078 /* NavigationStackModifier.swift */; }; + 1EE097042BACD66900780078 /* NavigationStackModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EE097022BACD66900780078 /* NavigationStackModifier.swift */; }; 1EF5FBD1250C109E00614FEA /* Encryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EF5FBD0250C109E00614FEA /* Encryption.swift */; }; 1EFEB5982493B6640072EDC0 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EFEB5972493B6640072EDC0 /* NotificationService.swift */; }; 1EFEB59C2493B6640072EDC0 /* NotificationService.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 1EFEB5952493B6640072EDC0 /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -570,8 +574,10 @@ 1EDFD0F92B589B8F002FEE5F /* MessagesLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesLoader.swift; sourceTree = ""; }; 1EDFD1052B58A66E002FEE5F /* CancelBag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelBag.swift; sourceTree = ""; }; 1EDFD1072B58AA77002FEE5F /* RoomsLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomsLoader.swift; sourceTree = ""; }; - 1EE096F62BACD1E400780078 /* NavigationCompatibleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationCompatibleView.swift; sourceTree = ""; }; 1EE096F92BACD1F200780078 /* ToolbarItemPlacement+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ToolbarItemPlacement+Extensions.swift"; sourceTree = ""; }; + 1EE096FC2BACD58300780078 /* LazyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyView.swift; sourceTree = ""; }; + 1EE096FF2BACD64C00780078 /* Binding+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Binding+Extensions.swift"; sourceTree = ""; }; + 1EE097022BACD66900780078 /* NavigationStackModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationStackModifier.swift; sourceTree = ""; }; 1EF5FBD0250C109E00614FEA /* Encryption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Encryption.swift; sourceTree = ""; }; 1EFEB5952493B6640072EDC0 /* NotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 1EFEB5972493B6640072EDC0 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; @@ -815,6 +821,7 @@ 1E29A31C2B5871B60093C03C /* Date+Extensions.swift */, 1E675B712BAC49B000438590 /* Color+Extensions.swift */, 1EE096F92BACD1F200780078 /* ToolbarItemPlacement+Extensions.swift */, + 1EE096FF2BACD64C00780078 /* Binding+Extensions.swift */, ); path = Extensions; sourceTree = ""; @@ -960,7 +967,8 @@ 1E638E982B5F0A2900E645E4 /* ChatScrollView.swift */, 1E06561C2B7E9C1C0081B01F /* MessageActionView.swift */, 1E388AC02B934CD4006FBDB0 /* RemoteImage.swift */, - 1EE096F62BACD1E400780078 /* NavigationCompatibleView.swift */, + 1EE096FC2BACD58300780078 /* LazyView.swift */, + 1EE097022BACD66900780078 /* NavigationStackModifier.swift */, ); path = Views; sourceTree = ""; @@ -2065,15 +2073,16 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1EE096FD2BACD58300780078 /* LazyView.swift in Sources */, 1E29A3242B5874FF0093C03C /* MessageComposerView.swift in Sources */, 1EB375892B55DBFB00AEC3D7 /* Server.swift in Sources */, 1E06561D2B7E9C1C0081B01F /* MessageActionView.swift in Sources */, 1EE096FA2BACD1F200780078 /* ToolbarItemPlacement+Extensions.swift in Sources */, 1E29A3162B5868DF0093C03C /* MessageListView.swift in Sources */, - 1EE096F72BACD1E400780078 /* NavigationCompatibleView.swift in Sources */, 1E4AFC172B5AF09C00E2AA7D /* Store.swift in Sources */, 1ED1EC8B2B86817100F6620C /* Deeplink.swift in Sources */, 1E29A2F42B585B070093C03C /* SubscriptionsResponse.swift in Sources */, + 1EE097002BACD64C00780078 /* Binding+Extensions.swift in Sources */, 1ED1EC892B867E2400F6620C /* ExtensionDelegate.swift in Sources */, 1E29A2F92B585B070093C03C /* SubscriptionsRequest.swift in Sources */, 1E29A2F22B585B070093C03C /* HistoryResponse.swift in Sources */, @@ -2094,6 +2103,7 @@ 1E06561B2B7E91FB0081B01F /* ErrorActionHandler.swift in Sources */, 1E29A2FC2B585B070093C03C /* SendMessageRequest.swift in Sources */, 1E29A30C2B585D1D0093C03C /* String+Extensions.swift in Sources */, + 1EE097032BACD66900780078 /* NavigationStackModifier.swift in Sources */, 1ED033CD2B55D671004F4930 /* RocketChatDatabase.swift in Sources */, 1E29A3122B5866090093C03C /* Room.swift in Sources */, 1E29A3032B585B070093C03C /* FailableDecodable.swift in Sources */, @@ -2147,15 +2157,16 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1EE096FE2BACD58300780078 /* LazyView.swift in Sources */, 1ED1EC902B86997F00F6620C /* MessageComposerView.swift in Sources */, 1ED1EC912B86997F00F6620C /* Server.swift in Sources */, 1ED1EC922B86997F00F6620C /* MessageActionView.swift in Sources */, 1EE096FB2BACD1F200780078 /* ToolbarItemPlacement+Extensions.swift in Sources */, 1ED1EC932B86997F00F6620C /* MessageListView.swift in Sources */, - 1EE096F82BACD1E400780078 /* NavigationCompatibleView.swift in Sources */, 1ED1EC942B86997F00F6620C /* Store.swift in Sources */, 1ED1EC952B86997F00F6620C /* Deeplink.swift in Sources */, 1ED1EC962B86997F00F6620C /* SubscriptionsResponse.swift in Sources */, + 1EE097012BACD64C00780078 /* Binding+Extensions.swift in Sources */, 1ED1EC972B86997F00F6620C /* ExtensionDelegate.swift in Sources */, 1ED1EC982B86997F00F6620C /* SubscriptionsRequest.swift in Sources */, 1ED1EC992B86997F00F6620C /* HistoryResponse.swift in Sources */, @@ -2176,6 +2187,7 @@ 1ED1ECA82B86997F00F6620C /* ErrorActionHandler.swift in Sources */, 1ED1ECA92B86997F00F6620C /* SendMessageRequest.swift in Sources */, 1ED1ECAA2B86997F00F6620C /* String+Extensions.swift in Sources */, + 1EE097042BACD66900780078 /* NavigationStackModifier.swift in Sources */, 1ED1ECAB2B86997F00F6620C /* RocketChatDatabase.swift in Sources */, 1ED1ECAC2B86997F00F6620C /* Room.swift in Sources */, 1ED1ECAD2B86997F00F6620C /* FailableDecodable.swift in Sources */,