New navigation flow

This commit is contained in:
Djorkaeff Alexandre 2024-03-23 07:29:35 -03:00
parent 99c035146c
commit 797076bb15
12 changed files with 158 additions and 79 deletions

View File

@ -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)
}

View File

@ -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()

View File

@ -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()
}
}

View File

@ -0,0 +1,24 @@
import SwiftUI
extension Binding where Value == Bool {
init<Wrapped>(bindingOptional: Binding<Wrapped?>) {
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<Wrapped>() -> Binding<Bool> where Value == Wrapped? {
Binding<Bool>(bindingOptional: self)
}
}

View File

@ -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(()))

View File

@ -0,0 +1,13 @@
import SwiftUI
struct LazyView<Content: View>: View {
private let build: () -> Content
init(_ build: @autoclosure @escaping () -> Content) {
self.build = build
}
var body: Content {
build()
}
}

View File

@ -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)
}
}

View File

@ -1,21 +0,0 @@
import SwiftUI
struct NavigationCompatibleView<Content: View>: 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()
}
}
}
}

View File

@ -0,0 +1,30 @@
import SwiftUI
struct NavigationStackModifier<Item, Destination: View>: ViewModifier {
let item: Binding<Item?>
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<Item, Destination: View>(
for binding: Binding<Item?>,
@ViewBuilder destination: @escaping (Item) -> Destination
) -> some View {
self.modifier(NavigationStackModifier(item: binding, destination: destination))
}
}

View File

@ -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()

View File

@ -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<Server> private var servers: FetchedResults<Server>
@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 {

View File

@ -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 = "<group>"; };
1EDFD1052B58A66E002FEE5F /* CancelBag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelBag.swift; sourceTree = "<group>"; };
1EDFD1072B58AA77002FEE5F /* RoomsLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomsLoader.swift; sourceTree = "<group>"; };
1EE096F62BACD1E400780078 /* NavigationCompatibleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationCompatibleView.swift; sourceTree = "<group>"; };
1EE096F92BACD1F200780078 /* ToolbarItemPlacement+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ToolbarItemPlacement+Extensions.swift"; sourceTree = "<group>"; };
1EE096FC2BACD58300780078 /* LazyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyView.swift; sourceTree = "<group>"; };
1EE096FF2BACD64C00780078 /* Binding+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Binding+Extensions.swift"; sourceTree = "<group>"; };
1EE097022BACD66900780078 /* NavigationStackModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationStackModifier.swift; sourceTree = "<group>"; };
1EF5FBD0250C109E00614FEA /* Encryption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Encryption.swift; sourceTree = "<group>"; };
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 = "<group>"; };
@ -815,6 +821,7 @@
1E29A31C2B5871B60093C03C /* Date+Extensions.swift */,
1E675B712BAC49B000438590 /* Color+Extensions.swift */,
1EE096F92BACD1F200780078 /* ToolbarItemPlacement+Extensions.swift */,
1EE096FF2BACD64C00780078 /* Binding+Extensions.swift */,
);
path = Extensions;
sourceTree = "<group>";
@ -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 = "<group>";
@ -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 */,