From 68cce0e5dd9641da75bd013632655ff15128f48c Mon Sep 17 00:00:00 2001 From: Kevin Date: Sun, 13 Oct 2024 21:22:40 +0700 Subject: [PATCH] update newfeed trigger --- .../Screens/SignIn/SignInView.swift | 5 +- .../xcdebugger/Breakpoints_v2.xcbkptlist | 440 +++++++++++++++--- .../xcschemes/xcschememanagement.plist | 4 +- .../FirebaseServiceConfig.swift | 9 + .../Screens/SignIn/SignInView.swift | 62 --- .../Screens/SignIn/SignInViewModel.swift | 73 --- .../Screens/Feed/MessageNewFeed.swift | 36 +- .../ViewModel/MessageNewFeedViewModel.swift | 133 +++++- .../UseCase/MessageUseCase.swift | 2 +- 9 files changed, 546 insertions(+), 218 deletions(-) delete mode 100644 Bookde/Feature/Authenticate/Sources/Authenticate/Screens/SignIn/SignInView.swift delete mode 100644 Bookde/Feature/Authenticate/Sources/Authenticate/Screens/SignIn/SignInViewModel.swift diff --git a/Authenticate/Sources/Authenticate/Screens/SignIn/SignInView.swift b/Authenticate/Sources/Authenticate/Screens/SignIn/SignInView.swift index 3036636..8f55c98 100644 --- a/Authenticate/Sources/Authenticate/Screens/SignIn/SignInView.swift +++ b/Authenticate/Sources/Authenticate/Screens/SignIn/SignInView.swift @@ -12,7 +12,7 @@ import AVFAudio import Combine public struct SignInView: View { - //MARK: Property + // MARK: Property @State private var email = "K@gmail.com" @State private var passworld = "02111997" @ObservedObject var viewModel: SignInViewModel @@ -21,6 +21,7 @@ public struct SignInView: View { @StateObject var cancelBag: CancelBag @StateObject var triggers: Triggers @EnvironmentObject private var router: Router + let navigateNewFeed: () -> Void public init(viewModel: SignInViewModel, navigateNewFeed: @escaping () -> Void = {}) { self.viewModel = viewModel @@ -94,8 +95,6 @@ public struct SignInView: View { final class Triggers: ObservableObject { var loginTrigger = PassthroughSubject() - } - } diff --git a/Bookde.xcodeproj/xcuserdata/kevin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Bookde.xcodeproj/xcuserdata/kevin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 72ba8b0..8c9d293 100644 --- a/Bookde.xcodeproj/xcuserdata/kevin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/Bookde.xcodeproj/xcuserdata/kevin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -110,13 +110,27 @@ filePath = "Bookde/Cores/DBService/Sources/DBFireBaseService/FirebaseServiceConfig.swift" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "203" - endingLineNumber = "203" + startingLineNumber = "212" + endingLineNumber = "212" landmarkName = "fetchMessage(toId:)" landmarkType = "7"> + + + startingLineNumber = "214" + endingLineNumber = "214"> + startingLineNumber = "214" + endingLineNumber = "214"> + startingLineNumber = "206" + endingLineNumber = "206"> + startingLineNumber = "214" + endingLineNumber = "214"> + startingLineNumber = "206" + endingLineNumber = "206"> + startingLineNumber = "214" + endingLineNumber = "214"> @@ -212,10 +226,40 @@ filePath = "Bookde/Cores/DBService/Sources/DBFireBaseService/FirebaseServiceConfig.swift" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "232" - endingLineNumber = "232" + startingLineNumber = "241" + endingLineNumber = "241" landmarkName = "fetchMessage(toId:)" landmarkType = "7"> + + + + + + @@ -244,24 +288,8 @@ filePath = "Bookde/Feature/MessageCenter/Sources/MessageCenter/Screens/Feed/MessageNewFeed.swift" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "52" - endingLineNumber = "52" - landmarkName = "body" - landmarkType = "24"> - - - - @@ -276,10 +304,68 @@ filePath = "Bookde/Cores/DBService/Sources/DBFireBaseService/FirebaseServiceConfig.swift" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "189" - endingLineNumber = "189" + startingLineNumber = "198" + endingLineNumber = "198" landmarkName = "fetchAllUsers()" landmarkType = "7"> + + + + + + + + + + @@ -333,15 +419,15 @@ @@ -349,17 +435,263 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Bookde.xcodeproj/xcuserdata/kevin.xcuserdatad/xcschemes/xcschememanagement.plist b/Bookde.xcodeproj/xcuserdata/kevin.xcuserdatad/xcschemes/xcschememanagement.plist index b0c8e85..cd30107 100644 --- a/Bookde.xcodeproj/xcuserdata/kevin.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Bookde.xcodeproj/xcuserdata/kevin.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,12 +7,12 @@ Bookde.xcscheme_^#shared#^_ orderHint - 4 + 0 BookdeScenario.xcscheme_^#shared#^_ orderHint - 5 + 2 Promises (Playground) 1.xcscheme diff --git a/Bookde/Cores/DBService/Sources/DBFireBaseService/FirebaseServiceConfig.swift b/Bookde/Cores/DBService/Sources/DBFireBaseService/FirebaseServiceConfig.swift index 6857aff..85febec 100644 --- a/Bookde/Cores/DBService/Sources/DBFireBaseService/FirebaseServiceConfig.swift +++ b/Bookde/Cores/DBService/Sources/DBFireBaseService/FirebaseServiceConfig.swift @@ -86,6 +86,7 @@ public final class ImplFireRepository: FireRepository { public func fetchCurrentUser() async -> Result { guard let uiid = Auth.auth().currentUser?.uid else { return .failure(FirbaseError.generic) } + print("uiid", uiid) do { let snapshot = try await Firestore.firestore() @@ -100,6 +101,14 @@ public final class ImplFireRepository: FireRepository { let document = DocumentDTO(dic: data) return .success(document) } catch { + + if let urlError = error as? URLError { + print("Network error: \(urlError)") + } else if let decodingError = error as? DecodingError { + print("Decoding error: \(decodingError)") + } else { + print("General error: \(error)") + } return .failure(error) } } diff --git a/Bookde/Feature/Authenticate/Sources/Authenticate/Screens/SignIn/SignInView.swift b/Bookde/Feature/Authenticate/Sources/Authenticate/Screens/SignIn/SignInView.swift deleted file mode 100644 index 556f284..0000000 --- a/Bookde/Feature/Authenticate/Sources/Authenticate/Screens/SignIn/SignInView.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// SwiftUIView.swift -// -// -// Created by Kevin on 10/22/23. -// - -import SwiftUI -import DBCore - -public struct SignInView: View { - @State private var email = "@gmail.com" - @State private var passworld = "02111997" - @EnvironmentObject var state: MyAuthenticateState - @ObservedObject var viewModel: SignInViewModel - - public init(viewModel: SignInViewModel) { - self.viewModel = viewModel - } - - public var body: some View { - VStack { - TextField("Email", text: self.$email) - .padding() - .background(Color.themeTextField) - .cornerRadius(20.0) - -// if viewModel.isLoading { -// ProgressView() -// .progressViewStyle(CircularProgressViewStyle(tint: .blue)) -// } - - SecureField("Password", text: self.$passworld) - .padding() - .background(Color.themeTextField) - .cornerRadius(20.0) - - Button(action: { - Task { - await viewModel.signUp(email: email, passworld: passworld) - } - }) { - Text("Sign In") - .font(.headline) - .foregroundColor(.white) - .padding() - .frame(width: 300, height: 50) - .background(Color.blue) - .cornerRadius(15.0) - } - - Button("Sign up now?") { - viewModel.didTapSignUp() - } - }.onAppear(perform: { - Task { - await viewModel.fetchCurrentUser() - } - }) - } -} - diff --git a/Bookde/Feature/Authenticate/Sources/Authenticate/Screens/SignIn/SignInViewModel.swift b/Bookde/Feature/Authenticate/Sources/Authenticate/Screens/SignIn/SignInViewModel.swift deleted file mode 100644 index 84e1bba..0000000 --- a/Bookde/Feature/Authenticate/Sources/Authenticate/Screens/SignIn/SignInViewModel.swift +++ /dev/null @@ -1,73 +0,0 @@ -// -// File.swift -// -// -// Created by Kevin on 10/22/23. -// - -import Foundation -import Combine -import DBCore -@MainActor -public final class SignInViewModel: ObservableObject { - - private let useCase: AuthenticateUseCase - @Published public var userProfile: UserProfile? - public var state = CurrentValueSubject(.none) - @Published public var appState: MyAuthenticateState = .init(id: "kevin") - @Published var isLoading: Bool = false - var hasFetchedUser = false - - public init(useCase: AuthenticateUseCase) { - self.useCase = useCase - Task { -// if !hasFetchedUser { -// await self.fetchCurrentUser() -// hasFetchedUser = true -// } - } - - } - - public func signUp(email: String, passworld: String) async { - isLoading = true - let user = await useCase.signIn(email: email, passworld: passworld) - switch user { - case .success(let user): - self.handleSignUpSuccess(user) - case .failure(let error): - self.handleSignUpFailure(error) - break - } - - } - - public func didTapSignUp() { - state.send(.startSignUp) - } - - private func handleSignUpSuccess(_ user: UserProfile) { - self.userProfile = user - self.isLoading = false - state.send(.finished) - appState.state = .finished - } - - private func handleSignUpFailure(_ error: Error) { - self.isLoading = false - } - - func fetchCurrentUser() async { - self.isLoading = true - let currentUser = await self.useCase.fetchCurrentUser() - switch currentUser { - case .success(_): - self.isLoading = false - self.state.send(.userAuthenticated) - case .failure(_): - break - } - } - - -} diff --git a/Bookde/Feature/MessageCenter/Sources/MessageCenter/Screens/Feed/MessageNewFeed.swift b/Bookde/Feature/MessageCenter/Sources/MessageCenter/Screens/Feed/MessageNewFeed.swift index 0328c8b..8d64fd7 100644 --- a/Bookde/Feature/MessageCenter/Sources/MessageCenter/Screens/Feed/MessageNewFeed.swift +++ b/Bookde/Feature/MessageCenter/Sources/MessageCenter/Screens/Feed/MessageNewFeed.swift @@ -8,6 +8,8 @@ import SwiftUI import CoreUI import Routers +import Combine +import DBCore public struct MessageFeedView: View { @State var shouldShowLogOutOptions = false @@ -15,6 +17,10 @@ public struct MessageFeedView: View { @State var showNewMessage: Bool = false @StateObject var viewModel: MessageNewFeedViewModel + @StateObject var output: MessageNewFeedViewModel.Output + @StateObject var triggers: Triggers + @StateObject var cancelBag: CancelBag + @EnvironmentObject private var router: Router let startCreateNewMessage: (UserChat) -> Void @@ -23,16 +29,27 @@ public struct MessageFeedView: View { viewModel: MessageNewFeedViewModel, startCreateNewMessage: @escaping (UserChat) -> Void = {_ in } ) { + let cancelBag = CancelBag() self.shouldShowLogOutOptions = shouldShowLogOutOptions _viewModel = .init(wrappedValue: viewModel) self.startCreateNewMessage = startCreateNewMessage + let triggers = Triggers() + let input = MessageNewFeedViewModel.Input( + loadTrigger: triggers.loadingTriggers.eraseToAnyPublisher() + ) + + let output = viewModel.transform(input: input, cancelBag: cancelBag) + _output = StateObject(wrappedValue: output) + _triggers = StateObject(wrappedValue: triggers) + _cancelBag = StateObject(wrappedValue: cancelBag) + triggers.loadingTriggers.send(()) } public var body: some View { ZStack { VStack { MessageHeaderSectionView( - user: viewModel.user, + user: output.currentUser ?? .init(email: "", profileUrl: "", uiid: ""), didTapLogOut: { Task { await viewModel.signOut() @@ -43,7 +60,7 @@ public struct MessageFeedView: View { } print("action ==>", actiion) }) - switch viewModel.messageStatus { + switch output.messageStatus { case .loading(let users): MessageListView( users: users, @@ -60,11 +77,15 @@ public struct MessageFeedView: View { self.startCreateNewMessage(user) } ) + + case .stop: + Text("Opp Something wrong") + Spacer() + default: + EmptyView() } }.onAppear(perform: { - Task { - await viewModel.fetch() - } + triggers.loadingTriggers.send(()) }) }.navigationBarHidden(true) @@ -93,4 +114,9 @@ public struct MessageFeedView: View { }) } + + final class Triggers: ObservableObject { + var loadingTriggers = PassthroughSubject() + } + } diff --git a/Bookde/Feature/MessageCenter/Sources/MessageCenter/Screens/Feed/ViewModel/MessageNewFeedViewModel.swift b/Bookde/Feature/MessageCenter/Sources/MessageCenter/Screens/Feed/ViewModel/MessageNewFeedViewModel.swift index 706ab1e..cff52bd 100644 --- a/Bookde/Feature/MessageCenter/Sources/MessageCenter/Screens/Feed/ViewModel/MessageNewFeedViewModel.swift +++ b/Bookde/Feature/MessageCenter/Sources/MessageCenter/Screens/Feed/ViewModel/MessageNewFeedViewModel.swift @@ -8,6 +8,7 @@ import Combine import SwiftUI import Foundation +import DBCore @MainActor final public class MessageNewFeedViewModel: ObservableObject { @@ -43,6 +44,7 @@ final public class MessageNewFeedViewModel: ObservableObject { enum Status { case loading(_ users: [UserChat]) case body(_ users: [UserChat]) + case stop } func fetch() async { @@ -62,24 +64,6 @@ final public class MessageNewFeedViewModel: ObservableObject { .store(in: &subscribers) } - private func loadDefaultUsers() -> [UserChat] { - let users: [UserChat] = [ - .init(email: "", profileUrl: "", uiid: "123"), - .init(email: "", profileUrl: "", uiid: "1256"), - .init(email: "", profileUrl: "", uiid: "124"), - .init(email: "", profileUrl: "", uiid: "1251"), - .init(email: "", profileUrl: "", uiid: "126"), - .init(email: "", profileUrl: "", uiid: "127"), - .init(email: "", profileUrl: "", uiid: "123"), - .init(email: "", profileUrl: "", uiid: "1256"), - .init(email: "", profileUrl: "", uiid: "124"), - .init(email: "", profileUrl: "", uiid: "1251"), - .init(email: "", profileUrl: "", uiid: "126"), - .init(email: "", profileUrl: "", uiid: "127") - ] - return users - } - func signOut() async { await useCase.signOut().assertNoFailure() .assign(to: &$isSignOut) @@ -89,3 +73,116 @@ final public class MessageNewFeedViewModel: ObservableObject { } } + +extension MessageNewFeedViewModel: ViewModelType { + + public final class Input: ObservableObject { + var loadTrigger: AnyPublisher + init(loadTrigger: AnyPublisher) { + self.loadTrigger = loadTrigger + } + + } + + public final class Output: ObservableObject { + @Published var currentUser: UserChat? + @Published var messageStatus: Status? + @Published var isLoading = false + @Published var errorMessage: String? + + } + + public func transform(input: Input, cancelBag: CancelBag) -> Output { + let output = Output() + input.loadTrigger + .handleEvents(receiveOutput: { [weak self] _ in + output.messageStatus = .loading((self?.loadDefaultUsers())!) + output.isLoading = true // Show loading indicator + }) + .flatMap { [weak self] _ -> AnyPublisher<(UserChat, [UserChat]), NSError> in + guard let self = self else { + return Fail<(UserChat, [UserChat]), NSError>( + error: NSError( + domain: "Deallocated", + code: -1, userInfo: nil + ) + ) + .eraseToAnyPublisher() + } + + return Future<(UserChat, [UserChat]), NSError> { promise in + Task { + do { + let currentUser = try await self.useCase.fetchCurrentUser() + .firstValue() // Using a helper to convert publisher to async value + + let allUsers = try await self.useCase.fetchAllUser() + .firstValue() // Same helper for fetching all us + // Resolve the future with the currentUser and allUsers + + // Resolve the Future with success, passing the tuple (currentUser, allUsers) + promise(.success((currentUser, allUsers))) + } catch { + output.messageStatus = .stop + // If an error occurs, resolve the Future with failure + promise(.failure(error as NSError)) + } + } + } + .eraseToAnyPublisher() + } + .sink(receiveCompletion: { completion in + output.isLoading = false // Hide loading indicator + switch completion { + case .finished: + break + case .failure(let error): + output.errorMessage = error.localizedDescription + } + }, receiveValue: { (currentUser, allUsers) in + // Assign the result to the output + output.currentUser = currentUser + output.messageStatus = .body(allUsers) + }) + .store(in: cancelBag) + + return output + } + + func loadDefaultUsers() -> [UserChat] { + let users: [UserChat] = [ + .init(email: "", profileUrl: "", uiid: "123"), + .init(email: "", profileUrl: "", uiid: "1256"), + .init(email: "", profileUrl: "", uiid: "124"), + .init(email: "", profileUrl: "", uiid: "1251"), + .init(email: "", profileUrl: "", uiid: "126"), + .init(email: "", profileUrl: "", uiid: "127"), + .init(email: "", profileUrl: "", uiid: "123"), + .init(email: "", profileUrl: "", uiid: "1256"), + .init(email: "", profileUrl: "", uiid: "124"), + .init(email: "", profileUrl: "", uiid: "1251"), + .init(email: "", profileUrl: "", uiid: "126"), + .init(email: "", profileUrl: "", uiid: "127") + ] + return users + } + +} + +extension Publisher { + func firstValue() async throws -> Output { + try await withCheckedThrowingContinuation { continuation in + var cancellable: AnyCancellable? + cancellable = self.first() + .sink(receiveCompletion: { completion in + if case let .failure(error) = completion { + continuation.resume(throwing: error) + } + cancellable?.cancel() // Cancel the subscription + }, receiveValue: { value in + continuation.resume(returning: value) + cancellable?.cancel() // Cancel the subscription after receiving the first value + }) + } + } +} diff --git a/Bookde/Feature/MessageCenter/Sources/MessageCenter/UseCase/MessageUseCase.swift b/Bookde/Feature/MessageCenter/Sources/MessageCenter/UseCase/MessageUseCase.swift index 8a49bbe..7f84d26 100644 --- a/Bookde/Feature/MessageCenter/Sources/MessageCenter/UseCase/MessageUseCase.swift +++ b/Bookde/Feature/MessageCenter/Sources/MessageCenter/UseCase/MessageUseCase.swift @@ -10,8 +10,8 @@ import DBFireBaseService import Foundation public protocol MessageUseCase { - func fetchCurrentUser() async -> AnyPublisher func signOut() async -> AnyPublisher + func fetchCurrentUser() async -> AnyPublisher func fetchAllUser() async -> AnyPublisher<[UserChat], Never> func send(toId: String, message: String) async -> AnyPublisher func fetchMessage(toId: String) async -> AnyPublisher<[MessageModel], Never>