diff --git a/project/Plugins/ConfigurationPlugin/ProjectDescriptionHelpers/InfoPlist.swift b/project/Plugins/ConfigurationPlugin/ProjectDescriptionHelpers/InfoPlist.swift index e7737ab5..2cf88964 100644 --- a/project/Plugins/ConfigurationPlugin/ProjectDescriptionHelpers/InfoPlist.swift +++ b/project/Plugins/ConfigurationPlugin/ProjectDescriptionHelpers/InfoPlist.swift @@ -30,6 +30,9 @@ public enum IdleInfoPlist { public static let exampleAppDefault: InfoPlist = .extendingDefault(with: [ "Privacy - Photo Library Usage Description" : "프로필 사진 설정을 위해 사진 라이브러리에 접근합니다.", + "NSAppTransportSecurity" : [ + "NSAllowsArbitraryLoads" : true + ], "UILaunchStoryboardName": "LaunchScreen.storyboard", "CFBundleDisplayName" : "$(BUNDLE_DISPLAY_NAME)", "UIApplicationSceneManifest": [ diff --git a/project/Projects/Data/ConcreteRepository/UserInfo/DefaultUserProfileRepository.swift b/project/Projects/Data/ConcreteRepository/UserInfo/DefaultUserProfileRepository.swift new file mode 100644 index 00000000..4cad3a36 --- /dev/null +++ b/project/Projects/Data/ConcreteRepository/UserInfo/DefaultUserProfileRepository.swift @@ -0,0 +1,80 @@ +// +// DefaultUserProfileRepository.swift +// ConcreteRepository +// +// Created by choijunios on 7/20/24. +// + +import Foundation +import RxSwift +import Entity +import RepositoryInterface +import NetworkDataSource + +public class DefaultUserProfileRepository: UserProfileRepository { + + let userInformationService: UserInformationService + let externalRequestService: ExternalRequestService + + public init(_ keyValueStore: KeyValueStore? = nil) { + + if let keyValueStore { + self.userInformationService = .init(keyValueStore: keyValueStore) + self.externalRequestService = .init(keyValueStore: keyValueStore) + } else { + self.userInformationService = .init() + self.externalRequestService = .init() + } + } + + public func getCenterProfile() -> Single { + + userInformationService + .requestDecodable(api: .getCenterProfile, with: .withToken) + .map { (dto: CenterProfileDTO) in dto.toEntity() } + } + + public func updateCenterProfileForText(phoneNumber: String, introduction: String?) -> Single { + userInformationService + .request(api: .updateCenterProfile( + officeNumber: phoneNumber, + introduce: introduction + ), with: .withToken) + .map { _ in return () } + } + + /// 이미지 업로드 + public func uploadImage(_ userType: UserType, imageInfo: ImageUploadInfo) -> Single { + + getPreSignedUrl(userType, ext: imageInfo.ext) + .flatMap { [unowned self] dto in + self.uploadImageToPreSignedUrl(url: dto.uploadUrl, data: imageInfo.data) + .map { _ in (id: dto.imageId, ext: dto.imageFileExtension) } + } + .flatMap { (id, ext) in + self.callbackToServerForUploadImageSuccess(userType, imageId: id, ext: ext) + } + } + + private func getPreSignedUrl(_ userType: UserType, ext: String) -> Single { + userInformationService + .request(api: .getPreSignedUrl(userType: userType, imageExt: ext), with: .withToken) + .map(ProfileImageUploadInfoDTO.self) + } + + private func uploadImageToPreSignedUrl(url: String, data: Data) -> Single { + externalRequestService + .request(api: .uploadImageToS3(url: url, data: data), with: .plain) + .map { _ in () } + } + + private func callbackToServerForUploadImageSuccess(_ userType: UserType, imageId: String, ext: String) -> Single { + userInformationService + .request(api: .imageUploadSuccessCallback( + userType: userType, + imageId: imageId, + imageExt: ext), with: .withToken + ) + .map { _ in () } + } +} diff --git a/project/Projects/Data/ConcretesTests/APITesting/TokenTesting.swift b/project/Projects/Data/ConcretesTests/APITesting/TokenTesting.swift index 5d380e0a..9e3a5dfc 100644 --- a/project/Projects/Data/ConcretesTests/APITesting/TokenTesting.swift +++ b/project/Projects/Data/ConcretesTests/APITesting/TokenTesting.swift @@ -10,9 +10,9 @@ import RxSwift @testable import NetworkDataSource // TestKeyValueStore -class TestKeyValueStore: KeyValueStore { +public class TestKeyValueStore: KeyValueStore { - init(testStore: [String : String] = [:]) { + public init(testStore: [String : String] = [:]) { self.testStore = [ Key.Auth.kaccessToken: "access_token", Key.Auth.krefreshToken: "refresh_token", @@ -21,22 +21,22 @@ class TestKeyValueStore: KeyValueStore { var testStore: [String: String] = [:] - func save(key: String, value: String) throws { + public func save(key: String, value: String) throws { testStore[key] = value } - func get(key: String) -> String? { + public func get(key: String) -> String? { return testStore[key] } - func delete(key: String) throws { + public func delete(key: String) throws { testStore.removeValue(forKey: key) } - func removeAll() throws { + public func removeAll() throws { testStore.removeAll() } diff --git a/project/Projects/Data/NetworkDataSource/API/Auth/AuthAPI.swift b/project/Projects/Data/NetworkDataSource/API/AuthAPI.swift similarity index 100% rename from project/Projects/Data/NetworkDataSource/API/Auth/AuthAPI.swift rename to project/Projects/Data/NetworkDataSource/API/AuthAPI.swift diff --git a/project/Projects/Data/NetworkDataSource/API/BaseAPI.swift b/project/Projects/Data/NetworkDataSource/API/BaseAPI.swift index 57634184..338c636c 100644 --- a/project/Projects/Data/NetworkDataSource/API/BaseAPI.swift +++ b/project/Projects/Data/NetworkDataSource/API/BaseAPI.swift @@ -11,6 +11,8 @@ import Moya public enum APIType { case auth + case users + case external(url: String) } // MARK: BaseAPI @@ -25,13 +27,13 @@ public extension BaseAPI { var baseStr = NetworkConfig.baseUrl - let apiVersion = "v1" - - baseStr += "/api/\(apiVersion)" - switch apiType { case .auth: baseStr += "/auth" + case .users: + baseStr += "/users" + case .external(let url): + baseStr = url } return URL(string: baseStr)! @@ -40,7 +42,10 @@ public extension BaseAPI { /// Default header var headers: [String : String]? { - return ["Content-Type": "application/json"] + switch apiType { + default: + ["Content-Type": "application/json"] + } } var validationType: ValidationType { .successCodes } diff --git a/project/Projects/Data/NetworkDataSource/API/ExtenalUrlAPI.swift b/project/Projects/Data/NetworkDataSource/API/ExtenalUrlAPI.swift new file mode 100644 index 00000000..3946bffa --- /dev/null +++ b/project/Projects/Data/NetworkDataSource/API/ExtenalUrlAPI.swift @@ -0,0 +1,48 @@ +// +// ExtenalUrlAPI.swift +// NetworkDataSource +// +// Created by choijunios on 7/20/24. +// + +import Foundation +import Moya +import Alamofire + +public enum ExtenalUrlAPI { + + case uploadImageToS3(url: String, data: Data) +} + +extension ExtenalUrlAPI: BaseAPI { + + public var apiType: APIType { + var baseUrl: String! + switch self { + case .uploadImageToS3(let url, _): + baseUrl = url + } + return .external(url: baseUrl) + } + + public var path: String { + switch self { + default: + "" + } + } + + public var method: Moya.Method { + switch self { + case .uploadImageToS3: + .put + } + } + + public var task: Moya.Task { + switch self { + case .uploadImageToS3(_, let data): + .requestData(data) + } + } +} diff --git a/project/Projects/Data/NetworkDataSource/API/UserInformationAPI.swift b/project/Projects/Data/NetworkDataSource/API/UserInformationAPI.swift new file mode 100644 index 00000000..5bb732b7 --- /dev/null +++ b/project/Projects/Data/NetworkDataSource/API/UserInformationAPI.swift @@ -0,0 +1,101 @@ +// +// UserInformationAPI.swift +// NetworkDataSource +// +// Created by choijunios on 7/20/24. +// + +import Foundation +import Moya +import Entity +import Alamofire + +extension UserType { + var pathUri: String { + switch self { + case .center: + "center" + case .worker: + "carer" + } + } +} + +public enum UserInformationAPI { + + // 프로필 조회 + case getCenterProfile + case updateCenterProfile(officeNumber: String, introduce: String?) + + // 프로필 사진 업로드 + case getPreSignedUrl(userType: UserType, imageExt: String) + case imageUploadSuccessCallback(userType: UserType, imageId: String, imageExt: String) + +// case getPreSignedUrlForProfile(type: UserType) +// case callbackForUpdateProfileImage(type: UserType) +} + +extension UserInformationAPI: BaseAPI { + + public var apiType: APIType { + .users + } + + public var path: String { + switch self { + case .getCenterProfile: + "center/my/profile" + case .updateCenterProfile: + "center/my/profile" + case .getPreSignedUrl(let type, _): + "\(type.pathUri)/my/profile-image/upload-url" + case .imageUploadSuccessCallback(let type, _, _): + "\(type.pathUri)/my/profile-image/upload-callback" + } + } + + public var method: Moya.Method { + switch self { + case .getCenterProfile: + .get + case .updateCenterProfile: + .post + case .getPreSignedUrl: + .get + case .imageUploadSuccessCallback: + .post + } + } + + var parameterEncoding: ParameterEncoding { + switch self { + default: + return JSONEncoding.default + } + } + + public var task: Moya.Task { + switch self { + case .getCenterProfile: + return .requestPlain + case .updateCenterProfile(let officeNumber, let introduce): + var bodyData: [String: String] = ["officeNumber": officeNumber] + if let introduce { + bodyData["introduce"] = introduce + } + return .requestParameters(parameters: bodyData, encoding: parameterEncoding) + case .getPreSignedUrl(_, let imageExt): + let params: [String: String] = [ + "imageFileExtension": imageExt + ] + return .requestParameters(parameters: params, encoding: URLEncoding.queryString) + case.imageUploadSuccessCallback(_, let imageId, let imageExt): + let params: [String: String] = [ + "imageId": imageId, + "imageFileExtension": imageExt + ] + return .requestParameters(parameters: params, encoding: parameterEncoding) + } + } + +} diff --git a/project/Projects/Data/NetworkDataSource/DTO/UserInfo/CenterProfileDTO.swift b/project/Projects/Data/NetworkDataSource/DTO/UserInfo/CenterProfileDTO.swift new file mode 100644 index 00000000..331f35b5 --- /dev/null +++ b/project/Projects/Data/NetworkDataSource/DTO/UserInfo/CenterProfileDTO.swift @@ -0,0 +1,38 @@ +// +// CenterProfileDTO.swift +// NetworkDataSource +// +// Created by choijunios on 7/20/24. +// + +import Foundation +import Entity + +public struct CenterProfileDTO: Codable { + let centerName: String? + let officeNumber: String? + let roadNameAddress: String? + let lotNumberAddress: String? + let detailedAddress: String? + let longitude: String? + let latitude: String? + let introduce: String? + let profileImageUrl: String? +} + +public extension CenterProfileDTO { + + func toEntity() -> CenterProfileVO { + CenterProfileVO( + centerName: centerName ?? "", + officeNumber: officeNumber ?? "", + roadNameAddress: roadNameAddress ?? "", + lotNumberAddress: lotNumberAddress ?? "", + detailedAddress: detailedAddress ?? "", + longitude: longitude ?? "", + latitude: latitude ?? "", + introduce: introduce ?? "", + profileImageURL: URL(string: profileImageUrl ?? "") + ) + } +} diff --git a/project/Projects/Data/NetworkDataSource/DTO/UserInfo/ProfileImageUploadInfoDTO.swift b/project/Projects/Data/NetworkDataSource/DTO/UserInfo/ProfileImageUploadInfoDTO.swift new file mode 100644 index 00000000..6e64241f --- /dev/null +++ b/project/Projects/Data/NetworkDataSource/DTO/UserInfo/ProfileImageUploadInfoDTO.swift @@ -0,0 +1,15 @@ +// +// ProfileImageUploadInfoDTO.swift +// NetworkDataSource +// +// Created by choijunios on 7/20/24. +// + +import Foundation + +public struct ProfileImageUploadInfoDTO: Decodable { + + public let imageId: String + public let imageFileExtension: String + public let uploadUrl: String +} diff --git a/project/Projects/Data/NetworkDataSource/Service/BaseNetworkService.swift b/project/Projects/Data/NetworkDataSource/Service/BaseNetworkService.swift index 1d759558..d9d0f9b6 100644 --- a/project/Projects/Data/NetworkDataSource/Service/BaseNetworkService.swift +++ b/project/Projects/Data/NetworkDataSource/Service/BaseNetworkService.swift @@ -195,7 +195,7 @@ public extension BaseNetworkService { _request( api: api, - provider: with == .plain ? self.providerWithoutToken : self.providerWithoutToken + provider: with == .plain ? self.providerWithoutToken : self.providerWithToken ) } @@ -243,6 +243,7 @@ public extension BaseNetworkService { // } } +// MARK: HTTPResponseException+Extension extension HTTPResponseException { init(response: Response) { diff --git a/project/Projects/Data/NetworkDataSource/Service/Auth/CenterRegisterService.swift b/project/Projects/Data/NetworkDataSource/Service/CenterRegisterService.swift similarity index 100% rename from project/Projects/Data/NetworkDataSource/Service/Auth/CenterRegisterService.swift rename to project/Projects/Data/NetworkDataSource/Service/CenterRegisterService.swift diff --git a/project/Projects/Data/NetworkDataSource/Service/ExternalRequestService.swift b/project/Projects/Data/NetworkDataSource/Service/ExternalRequestService.swift new file mode 100644 index 00000000..5aa17e1f --- /dev/null +++ b/project/Projects/Data/NetworkDataSource/Service/ExternalRequestService.swift @@ -0,0 +1,17 @@ +// +// ExternalRequestService.swift +// NetworkDataSource +// +// Created by choijunios on 7/20/24. +// + +import Foundation + +public class ExternalRequestService: BaseNetworkService { + + public init() { } + + public override init(keyValueStore: KeyValueStore) { + super.init(keyValueStore: keyValueStore) + } +} diff --git a/project/Projects/Data/NetworkDataSource/Service/UserInformationService.swift b/project/Projects/Data/NetworkDataSource/Service/UserInformationService.swift new file mode 100644 index 00000000..286d5ae3 --- /dev/null +++ b/project/Projects/Data/NetworkDataSource/Service/UserInformationService.swift @@ -0,0 +1,17 @@ +// +// UserInformationService.swift +// NetworkDataSource +// +// Created by choijunios on 7/20/24. +// + +import Foundation + +public class UserInformationService: BaseNetworkService { + + public init() { } + + public override init(keyValueStore: KeyValueStore) { + super.init(keyValueStore: keyValueStore) + } +} diff --git a/project/Projects/Domain/ConcreteUseCase/UserInfo/DefaultCenterProfileUseCase.swift b/project/Projects/Domain/ConcreteUseCase/UserInfo/DefaultCenterProfileUseCase.swift new file mode 100644 index 00000000..632847ed --- /dev/null +++ b/project/Projects/Domain/ConcreteUseCase/UserInfo/DefaultCenterProfileUseCase.swift @@ -0,0 +1,92 @@ +// +// DefaultCenterProfileUseCase.swift +// ConcreteUseCase +// +// Created by choijunios on 7/20/24. +// + +import Foundation +import RxSwift +import Entity +import UseCaseInterface +import RepositoryInterface + +public class DefaultCenterProfileUseCase: CenterProfileUseCase { + + let repository: UserProfileRepository + + public init(repository: UserProfileRepository) { + self.repository = repository + } + + public func getProfile() -> Single> { + convert(task: repository + .getCenterProfile()) { [unowned self] error in + toDomainError(error: error) + } + } + + public func updateProfile(phoneNumber: String?, introduction: String?, imageInfo: ImageUploadInfo?) -> Single> { + + var updateText: Single! + var updateImage: Single! + + if let phoneNumber { + updateText = repository.updateCenterProfileForText( + phoneNumber: phoneNumber, + introduction: introduction + ) + } else { + updateText = .just(()) + } + + if let imageInfo { + updateImage = repository.uploadImage( + .center, + imageInfo: imageInfo + ) + } else { + updateImage = .just(()) + } + + let updateTextResult = updateText + .catch { error in + if let httpExp = error as? HTTPResponseException { + let newError = HTTPResponseException( + status: httpExp.status, + rawCode: "Err-001", + timeStamp: httpExp.timeStamp + ) + + return .error(newError) + } + return .error(error) + } + + let updateImageResult = updateImage + .catch { error in + if let httpExp = error as? HTTPResponseException { + let newError = HTTPResponseException( + status: httpExp.status, + rawCode: "Err-002", + timeStamp: httpExp.timeStamp + ) + + return .error(newError) + } + return .error(error) + } + + let task = Observable + .zip( + updateTextResult.asObservable(), + updateImageResult.asObservable() + ) + .map { _ in () } + .asSingle() + + return convert(task: task) { [unowned self] error in + toDomainError(error: error) + } + } +} diff --git a/project/Projects/Domain/Entity/Error/UserInfo/UserInfoError.swift b/project/Projects/Domain/Entity/Error/UserInfo/UserInfoError.swift new file mode 100644 index 00000000..137dee3c --- /dev/null +++ b/project/Projects/Domain/Entity/Error/UserInfo/UserInfoError.swift @@ -0,0 +1,28 @@ +// +// UserInfoError.swift +// Entity +// +// Created by choijunios on 7/20/24. +// + +import Foundation + +public enum UserInfoError: String, DomainError { + + case textUpdateFailed = "Err-001" + case imageUpdateFailed = "Err-002" + + // undefinedError + case undefinedError="Err-000" + + public var message: String { + switch self { + case .undefinedError: + "❌ \(String(describing: Self.self)) 정의되지 않은 에러타입입니다. ❌" + case .imageUpdateFailed: + "이미지 업로드에 실패했습니다." + case .textUpdateFailed: + "프로필 업로드에 실패했습니다." + } + } +} diff --git a/project/Projects/Domain/Entity/State/Auth/Worker/WorkerRegisterState.swift b/project/Projects/Domain/Entity/State/Auth/Worker/WorkerRegisterState.swift index 1c0bbdf3..abbc0331 100644 --- a/project/Projects/Domain/Entity/State/Auth/Worker/WorkerRegisterState.swift +++ b/project/Projects/Domain/Entity/State/Auth/Worker/WorkerRegisterState.swift @@ -7,23 +7,6 @@ import Foundation -public enum Gender { - case notDetermined - case male - case female - - public var str: String { - switch self { - case .notDetermined: - "무결" - case .male: - "남성" - case .female: - "여성" - } - } -} - public class WorkerRegisterState { public var name: String = "" public var gender: Gender = .notDetermined diff --git a/project/Projects/Domain/Entity/State/User/Gender.swift b/project/Projects/Domain/Entity/State/User/Gender.swift new file mode 100644 index 00000000..fd130d28 --- /dev/null +++ b/project/Projects/Domain/Entity/State/User/Gender.swift @@ -0,0 +1,25 @@ +// +// Gender.swift +// Entity +// +// Created by choijunios on 7/21/24. +// + +import Foundation + +public enum Gender { + case notDetermined + case male + case female + + public var twoLetterKoreanWord: String { + switch self { + case .notDetermined: + "무결" + case .male: + "남성" + case .female: + "여성" + } + } +} diff --git a/project/Projects/Domain/Entity/State/User/UserType.swift b/project/Projects/Domain/Entity/State/User/UserType.swift new file mode 100644 index 00000000..355e912a --- /dev/null +++ b/project/Projects/Domain/Entity/State/User/UserType.swift @@ -0,0 +1,13 @@ +// +// UserType.swift +// Entity +// +// Created by choijunios on 7/20/24. +// + +import Foundation + +public enum UserType { + case center + case worker +} diff --git a/project/Projects/Domain/Entity/Transport/ImageUploadInfo.swift b/project/Projects/Domain/Entity/Transport/ImageUploadInfo.swift new file mode 100644 index 00000000..2faf980e --- /dev/null +++ b/project/Projects/Domain/Entity/Transport/ImageUploadInfo.swift @@ -0,0 +1,18 @@ +// +// ImageUploadInfo.swift +// Entity +// +// Created by choijunios on 7/20/24. +// + +import Foundation + +public struct ImageUploadInfo { + public let data: Data + public let ext: String + + public init(data: Data, ext: String) { + self.data = data + self.ext = ext + } +} diff --git a/project/Projects/Domain/Entity/VO/UserInfo/CenterProfileVO.swift b/project/Projects/Domain/Entity/VO/UserInfo/CenterProfileVO.swift new file mode 100644 index 00000000..c34d6d11 --- /dev/null +++ b/project/Projects/Domain/Entity/VO/UserInfo/CenterProfileVO.swift @@ -0,0 +1,32 @@ +// +// CenterProfileVO.swift +// Entity +// +// Created by choijunios on 7/20/24. +// + +import Foundation + +public class CenterProfileVO { + public let centerName: String + public let officeNumber: String + public let roadNameAddress: String + public let lotNumberAddress: String + public let detailedAddress: String + public let longitude: String + public let latitude: String + public let introduce: String + public let profileImageURL: URL? + + public init(centerName: String, officeNumber: String, roadNameAddress: String, lotNumberAddress: String, detailedAddress: String, longitude: String, latitude: String, introduce: String, profileImageURL: URL?) { + self.centerName = centerName + self.officeNumber = officeNumber + self.roadNameAddress = roadNameAddress + self.lotNumberAddress = lotNumberAddress + self.detailedAddress = detailedAddress + self.longitude = longitude + self.latitude = latitude + self.introduce = introduce + self.profileImageURL = profileImageURL + } +} diff --git a/project/Projects/Domain/RepositoryInterface/UserInfo/UserProfileRepository.swift b/project/Projects/Domain/RepositoryInterface/UserInfo/UserProfileRepository.swift new file mode 100644 index 00000000..cbc6ca84 --- /dev/null +++ b/project/Projects/Domain/RepositoryInterface/UserInfo/UserProfileRepository.swift @@ -0,0 +1,19 @@ +// +// UserProfileRepository.swift +// RepositoryInterface +// +// Created by choijunios on 7/20/24. +// + +import Foundation +import RxSwift +import Entity + +public protocol UserProfileRepository: RepositoryBase { + + func getCenterProfile() -> Single + func updateCenterProfileForText(phoneNumber: String, introduction: String?) -> Single + + // ImageUpload + func uploadImage(_ userType: UserType, imageInfo: ImageUploadInfo) -> Single +} diff --git a/project/Projects/Domain/UseCaseInterface/UserInfo/CenterProfileUseCase.swift b/project/Projects/Domain/UseCaseInterface/UserInfo/CenterProfileUseCase.swift new file mode 100644 index 00000000..7809cfc0 --- /dev/null +++ b/project/Projects/Domain/UseCaseInterface/UserInfo/CenterProfileUseCase.swift @@ -0,0 +1,21 @@ +// +// CenterProfileUseCase.swift +// UseCaseInterface +// +// Created by choijunios on 7/20/24. +// + +import Foundation +import RxSwift +import Entity + +/// 1. 센터 프로필 정보 조회 +/// 2. 센터 프로필 정보 업데이트(전화번호, 센터소개글) +/// 3. 센터 프로필 정보 업데이트(이미지, pre-signed-url) +/// 4. 센터 프로필 정보 업데이트(이미지, pre-signed-url-callback) + +public protocol CenterProfileUseCase: UseCaseBase { + + func getProfile() -> Single> + func updateProfile(phoneNumber: String?, introduction: String?, imageInfo: ImageUploadInfo?) -> Single> +} diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/activestar.imageset/Contents.json b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/activestar.imageset/Contents.json deleted file mode 100644 index 77971f97..00000000 --- a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/activestar.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "activestar.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "activestar 1.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "activestar 2.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/activestar.imageset/activestar 1.png b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/activestar.imageset/activestar 1.png deleted file mode 100644 index a73e66a3..00000000 Binary files a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/activestar.imageset/activestar 1.png and /dev/null differ diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/activestar.imageset/activestar 2.png b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/activestar.imageset/activestar 2.png deleted file mode 100644 index a73e66a3..00000000 Binary files a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/activestar.imageset/activestar 2.png and /dev/null differ diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/activestar.imageset/activestar.png b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/activestar.imageset/activestar.png deleted file mode 100644 index a73e66a3..00000000 Binary files a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/activestar.imageset/activestar.png and /dev/null differ diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/star.imageset/Contents.json b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/star.imageset/Contents.json deleted file mode 100644 index 941788f9..00000000 --- a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/star.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "start.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "start 1.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "start 2.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/star.imageset/start 1.png b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/star.imageset/start 1.png deleted file mode 100644 index c07908cd..00000000 Binary files a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/star.imageset/start 1.png and /dev/null differ diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/star.imageset/start 2.png b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/star.imageset/start 2.png deleted file mode 100644 index c07908cd..00000000 Binary files a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/star.imageset/start 2.png and /dev/null differ diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/star.imageset/start.png b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/star.imageset/start.png deleted file mode 100644 index c07908cd..00000000 Binary files a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/star.imageset/start.png and /dev/null differ diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/subscribeStar.imageset/Contents.json b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/subscribeStar.imageset/Contents.json new file mode 100644 index 00000000..e8936827 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/subscribeStar.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "subscribeStar.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/subscribeStar.imageset/subscribeStar.svg b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/subscribeStar.imageset/subscribeStar.svg new file mode 100644 index 00000000..3e402271 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/subscribeStar.imageset/subscribeStar.svg @@ -0,0 +1,3 @@ + + + diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/WorkerEmployCard.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/WorkerEmployCard.swift index 582f365b..649de83b 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/WorkerEmployCard.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/WorkerEmployCard.swift @@ -13,11 +13,11 @@ import Entity public class WorkerEmployCard: UITableViewCell { // View - let starButton: IconStateButton = { - let button = IconStateButton( - normal: DSKitAsset.Icons.star.image, - accent: DSKitAsset.Icons.activestar.image, - initial: .normal + let starButton: IconWithColorStateButton = { + let button = IconWithColorStateButton( + representImage: DSKitAsset.Icons.subscribeStar.image, + normalColor: DSKitAsset.Colors.gray200.color, + accentColor: DSKitAsset.Colors.orange300.color ) return button }() @@ -254,7 +254,7 @@ public class WorkerEmployCard: UITableViewCell { dayLeftTag.textString = "D-\(mock.dayLeft)" titleLabel.textString = mock.title timeTakenForWalkLabel.textString = mock.timeTakenForWalk - serviceTargetInfoLabel.textString = "\(mock.targetLevel)등급 \(mock.targetAge)세 \(mock.targetGender.str)" + serviceTargetInfoLabel.textString = "\(mock.targetLevel)등급 \(mock.targetAge)세 \(mock.targetGender.twoLetterKoreanWord)" workDaysLabel.textString = mock.days.map({ $0.rawValue }).joined(separator: ",") workTimeLabel.textString = "\(mock.startTime) - \(mock.endTime)" payPerHourLabel.textString = "시급 \(mock.payPerHour) 원" diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/IconStateButton.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/IconStateButton.swift index 957bdb11..fc794fec 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/IconStateButton.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/IconStateButton.swift @@ -8,32 +8,27 @@ import UIKit import RxCocoa -public class IconStateButton: UIImageView { +public class IconWithColorStateButton: UIImageView { // Init values public private(set) var state: State - public var normalImage: UIImage - public var accentImage: UIImage + public var representImage: UIImage + public var normalColor: UIColor + public var accentColor: UIColor public let eventPublisher: PublishRelay = .init() - // View - let label: IdleLabel = { - - let view = IdleLabel(typography: .Body3) - - return view - }() - public init( - normal: UIImage, - accent: UIImage, - initial: State) + representImage: UIImage, + normalColor: UIColor, + accentColor: UIColor, + initial: State = .normal) { self.state = initial - self.normalImage = normal - self.accentImage = accent + self.representImage = representImage + self.normalColor = normalColor + self.accentColor = accentColor super.init(frame: .zero) @@ -48,6 +43,11 @@ public class IconStateButton: UIImageView { private func setAppearance() { self.contentMode = .scaleAspectFit + + // 이미지를 템플릿 모드로 변경 + let templateImage = self.representImage.withRenderingMode(.alwaysTemplate) + self.image = templateImage + setState(.normal) } @@ -73,17 +73,17 @@ public class IconStateButton: UIImageView { eventPublisher.accept(state) - let nextImage = state == .normal ? normalImage : accentImage + let nextColor = state == .normal ? normalColor : accentColor // UIView.animate - 뷰 속성 변화에 적합 // UIView.tranistion - 뷰컨텐츠변화 혹은 뷰자체에 대한 변화, 이미지의 경우 여기 해당한다. UIView.transition(with: self, duration: withAnimation ? 0.1 : 0.0, options: .transitionCrossDissolve, animations: { [weak self] in - self?.image = nextImage + self?.tintColor = nextColor }, completion: nil) } } -public extension IconStateButton { +public extension IconWithColorStateButton { enum State { case normal, accent diff --git a/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/SceneDelegate.swift b/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/SceneDelegate.swift index 29606e91..5106809f 100644 --- a/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/SceneDelegate.swift +++ b/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/SceneDelegate.swift @@ -7,6 +7,8 @@ import UIKit import CenterFeature +import ConcreteUseCase +import ConcreteRepository class SceneDelegate: UIResponder, UIWindowSceneDelegate { @@ -16,15 +18,29 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { guard let windowScene = scene as? UIWindowScene else { return } - window = UIWindow(windowScene: windowScene) - let viewModel = CenterProfileViewModel() - let viewController = CenterProfileViewController() + let store = TestStore() + + try! store.saveAuthToken( + accessToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOm51bGwsInN1YiI6bnVsbCwiaXNzIjoiM2lkaW90cyIsImlhdCI6MTcyMTUzMDYwMCwibmJmIjoxNzIxNTMwNjAwLCJleHAiOjE3MjE1MzEyMDAsInR5cGUiOiJBQ0NFU1NfVE9LRU4iLCJ1c2VySWQiOiIwMTkwZDMzOC0zZjg0LTc3M2MtOTZhYy01MzZlODg2ZjBkMjUiLCJwaG9uZU51bWJlciI6IjAxMC00NDQ0LTUyMzIifQ.dA9TrFJFDL715ram0uaShCjqRPI8t8iZ39ZJn7oHu6E", + refreshToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOm51bGwsInN1YiI6bnVsbCwiaXNzIjoiM2lkaW90cyIsImlhdCI6MTcyMTQ4OTczMCwibmJmIjoxNzIxNDg5NzMwLCJleHAiOjE3MjI2OTkzMzAsInR5cGUiOiJSRUZSRVNIX1RPS0VOIiwidXNlcklkIjoiMDE5MGNmNDgtM2RjNi03ZWVkLTk4OGUtYTA5N2EwZDEwYjMzIn0.Hx4t09U3ra5RbYvwjl3flQccw6-hBMWUxY6zI_eVpiQ" + ) + + let useCase = DefaultCenterProfileUseCase( + repository: DefaultUserProfileRepository(store) + ) - viewController.bind(viewModel: viewModel) + let viewModel = CenterProfileViewModel( + useCase: useCase + ) - window?.rootViewController = viewController + let vc = CenterProfileViewController() + + vc.bind(viewModel: viewModel) + + window = UIWindow(windowScene: windowScene) + window?.rootViewController = vc window?.makeKeyAndVisible() } } diff --git a/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/Testing.swift b/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/Testing.swift new file mode 100644 index 00000000..c8aff834 --- /dev/null +++ b/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/Testing.swift @@ -0,0 +1,28 @@ +// +// Testing.swift +// Center_ExampleApp +// +// Created by choijunios on 7/20/24. +// + +import Foundation +import NetworkDataSource + +class TestStore: KeyValueStore { + func save(key: String, value: String) throws { + UserDefaults.standard.setValue(value, forKey: key) + } + + func get(key: String) -> String? { + UserDefaults.standard.string(forKey: key) + } + + func delete(key: String) throws { + + } + + func removeAll() throws { + + } + +} diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/Profile/CenterProfileViewController.swift b/project/Projects/Presentation/Feature/Center/Sources/View/Profile/CenterProfileViewController.swift index e9bb3a8b..61ea6fd1 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/View/Profile/CenterProfileViewController.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/View/Profile/CenterProfileViewController.swift @@ -17,16 +17,15 @@ public protocol CenterProfileViewModelable where Input: CenterProfileInputable, associatedtype Output var input: Input { get } var output: Output? { get } - - func requestData() } public protocol CenterProfileInputable { + var readyToFetch: PublishRelay { get } var editingButtonPressed: PublishRelay { get } var editingFinishButtonPressed: PublishRelay { get } var editingPhoneNumber: BehaviorRelay { get } var editingInstruction: BehaviorRelay { get } - var editingImage: BehaviorRelay { get } + var selectedImage: PublishRelay { get } } public protocol CenterProfileOutputable { @@ -34,7 +33,7 @@ public protocol CenterProfileOutputable { var centerLocation: Driver { get } var centerPhoneNumber: Driver { get } var centerIntroduction: Driver { get } - var centerImage: Driver { get } + var displayingImage: Driver { get } var isEditingMode: Driver { get } var editingValidation: Driver { get } var alert: Driver { get } @@ -122,6 +121,7 @@ public class CenterProfileViewController: DisposableViewController { /// 센터 소개가 표시되는 라벨 let centerIntroductionLabel: IdleLabel = { let label = IdleLabel(typography: .Body3) + label.numberOfLines = 0 return label }() /// 센터 소개를 수정하는 텍스트 필드 @@ -358,6 +358,12 @@ public class CenterProfileViewController: DisposableViewController { // input let input = viewModel.input + let bindFinished = PublishRelay() + + bindFinished + .bind(to: input.readyToFetch) + .disposed(by: disposeBag) + profileEditButton .eventPublisher .bind(to: input.editingButtonPressed) @@ -379,7 +385,7 @@ public class CenterProfileViewController: DisposableViewController { .disposed(by: disposeBag) edtingImage - .bind(to: input.editingImage) + .bind(to: input.selectedImage) .disposed(by: disposeBag) // output @@ -414,7 +420,7 @@ public class CenterProfileViewController: DisposableViewController { .disposed(by: disposeBag) output - .centerImage + .displayingImage .drive(centerImageView.rx.image) .disposed(by: disposeBag) @@ -452,7 +458,8 @@ public class CenterProfileViewController: DisposableViewController { } .disposed(by: disposeBag) - viewModel.requestData() + // 바인딩 종료 + bindFinished.accept(()) } public func showAlert(vo: DefaultAlertContentVO) { @@ -498,8 +505,6 @@ extension CenterProfileViewController: UIImagePickerControllerDelegate, UINaviga if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage { edtingImage.accept(image) - centerImageView.image = image - picker.dismiss(animated: true) } } diff --git a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/Profile/CenterProfileViewModel.swift b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/Profile/CenterProfileViewModel.swift index 0c2ec4f4..7026d87e 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/Profile/CenterProfileViewModel.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/Profile/CenterProfileViewModel.swift @@ -10,6 +10,7 @@ import Entity import RxSwift import RxCocoa import PresentationCore +import UseCaseInterface public struct ChangeCenterInformation { let phoneNumber: String? @@ -19,75 +20,163 @@ public struct ChangeCenterInformation { public class CenterProfileViewModel: CenterProfileViewModelable { + let profileUseCase: CenterProfileUseCase + public var input: Input public var output: Output? = nil - func checkModification( - prev_phoneNumber: String, - prev_introduction: String, - prev_image: UIImage) -> (String?, String?, UIImage?) - { - ( - input.editingPhoneNumber.value == prev_phoneNumber ? nil : input.editingPhoneNumber.value, - input.editingInstruction.value == prev_introduction ? nil : input.editingInstruction.value, - input.editingImage.value == prev_image ? nil : input.editingImage.value + private var fetchedPhoneNumber: String? + private var fetchedIntroduction: String? + private var fetchedImage: UIImage? + + private var editingImageInfo: ImageUploadInfo? + + func checkModification() -> (String?, String?, ImageUploadInfo?) { + + let phoneNumber = input.editingPhoneNumber.value + let instruction = input.editingInstruction.value + + return ( + phoneNumber == fetchedPhoneNumber ? nil : phoneNumber, + instruction == fetchedIntroduction ? nil : instruction, + editingImageInfo ) } - public init() { + public init(useCase: CenterProfileUseCase) { + + self.profileUseCase = useCase + self.input = Input() + // MARK: fetch from server + let profileRequestResult = input + .readyToFetch + .flatMap { [unowned self] _ in + self.profileUseCase.getProfile() + } + .share() + + let profileRequestSuccess = profileRequestResult + .compactMap { $0.value } + + let profileRequestFailure = profileRequestResult + .compactMap { $0.error } + .map { error in + DefaultAlertContentVO(title: "프로필 정보 불러오기 실패", message: error.message) + } + + let centerNameDriver = profileRequestSuccess + .map { $0.centerName } + .asDriver(onErrorJustReturn: "") + + let centerAddressDriver = profileRequestSuccess + .map { $0.roadNameAddress } + .asDriver(onErrorJustReturn: "") + + let centerIntroductionDriver = profileRequestSuccess + .map { [weak self] in + let introduce = $0.introduce + self?.fetchedIntroduction = introduce + return introduce + } + .asDriver(onErrorJustReturn: "") + + let centerPhoneNumberDriver = profileRequestSuccess + .map { [weak self] in + let phoneNumber = $0.officeNumber + self?.fetchedPhoneNumber = phoneNumber + return phoneNumber + } + .asDriver(onErrorJustReturn: "") + + let fetchCenterImage = profileRequestSuccess + .map { $0.profileImageURL } + .compactMap { $0 } + .observe(on: OperationQueueScheduler.init(operationQueue: .init(), queuePriority: .high)) + .map({ [weak self] imageUrl in + if let data = try? Data(contentsOf: imageUrl) { + let image = UIImage(data: data) + self?.fetchedImage = image + return image + } + return nil + }) + + // MARK: image validation + let imageValidationResult = input + .selectedImage + .map { [unowned self] image -> UIImage? in + guard let imageInfo = self.validateSelectedImage(image: image) else { return nil } + printIfDebug("✅ 업로드 가능한 이미지 타입 \(imageInfo.ext)") + self.editingImageInfo = imageInfo + return image + } + .share() + + let imageValidationFailure = imageValidationResult + .filter { $0 == nil } + .map { _ in + DefaultAlertContentVO( + title: "이미지 선택 오류", + message: "지원하지 않는 이미지 형식입니다." + ) + } + + let displayingImageDriver = Observable + .merge( + fetchCenterImage, + imageValidationResult.compactMap { $0 } + ) + .asDriver(onErrorJustReturn: .init()) + + // 최신 값들 + 버튼이 눌릴 경우 변경 로직이 실행된다. let editingRequestResult = input .editingFinishButtonPressed .map({ [unowned self] _ in - self.checkModification( - prev_phoneNumber: self.input.centerPhoneNumber.value, - prev_introduction: self.input.centerIntroduction.value, - prev_image: self.input.centerImage.value - ) + checkModification() }) - .flatMap { (inputs) in + .flatMap { [useCase] (inputs) in - let (phoneNumber, introduction, image) = inputs + let (phoneNumber, introduction, imageInfo) = inputs // 변경이 발생하지 않은 곳은 nil값이 전달된다. + if let _ = phoneNumber { printIfDebug("✅ 전화번호 변경되었음") } + if let _ = introduction { printIfDebug("✅ 센터소개 변경되었음") } + if let _ = imageInfo { printIfDebug("✅ 센터 이미지 변경되었음") } - // API 호출 - return Single.just(Result.success( - ChangeCenterInformation( - phoneNumber: phoneNumber, - introduction: introduction, - image: image - ) - )) + return useCase.updateProfile( + phoneNumber: phoneNumber, + introduction: introduction, + imageInfo: imageInfo + ) } .share() - // 스트림을 유지하기위해 생성한 Driver로 필수적으로 사용되지 않는다. let editingValidation = editingRequestResult .compactMap { $0.value } - .map { [weak input] info in + .map { [input] info in - if let phoneNumber = info.phoneNumber { - printIfDebug("✅ 전화번호 변경 반영되었음") - input?.centerPhoneNumber.accept(phoneNumber) - } - - if let introduction = info.introduction { - printIfDebug("✅ 센터소개 반영되었음") - input?.centerIntroduction.accept(introduction) - } + printIfDebug("✅ 정보가 성공적으로 업데이트됨") - if let image = info.image { - printIfDebug("✅ 센터 이미지 변경 반영되었음") - input?.centerImage.accept(image) - } + // 업데이트된 정보 요청 + input.readyToFetch.accept(()) return () } .asDriver(onErrorJustReturn: ()) + let editingRequestFailure = editingRequestResult + .compactMap({ $0.error }) + .map({ error in + // 변경 실패 Alert + return DefaultAlertContentVO( + title: "변경 실패", + message: "변경 싪패 이유" + ) + }) + enum Mode { case editing, display } @@ -116,37 +205,33 @@ public class CenterProfileViewModel: CenterProfileViewModelable { .asDriver(onErrorJustReturn: false) - let alertDriver = editingRequestResult - .compactMap({ $0.error }) - .map({ error in - // 변경 실패 Alert - return DefaultAlertContentVO( - title: "변경 실패", - message: "변경 싪패 이유" - ) - }) + let alertDriver = Observable + .merge( + profileRequestFailure, + editingRequestFailure, + imageValidationFailure + ) .asDriver(onErrorJustReturn: .default) self.output = .init( - centerName: input.centerName.asDriver(onErrorJustReturn: ""), - centerLocation: input.centerLocation.asDriver(onErrorJustReturn: ""), - centerPhoneNumber: input.centerPhoneNumber.asDriver(onErrorJustReturn: ""), - centerIntroduction: input.centerIntroduction.asDriver(onErrorJustReturn: ""), - centerImage: input.centerImage.asDriver(onErrorJustReturn: UIImage()), + centerName: centerNameDriver, + centerLocation: centerAddressDriver, + centerPhoneNumber: centerPhoneNumberDriver, + centerIntroduction: centerIntroductionDriver, + displayingImage: displayingImageDriver, isEditingMode: isEditingMode, editingValidation: editingValidation, alert: alertDriver ) } - public func requestData() { - - // 서버로 부터 데이터를 요청하는 API - input.centerName.accept("네 얼간이 방문요양센터") - input.centerLocation.accept("강남구 삼성동 512-3") - input.centerPhoneNumber.accept("(02) 123-4567") - input.centerIntroduction.accept("안녕하세요 반갑습니다!") - input.centerImage.accept(UIImage()) + func validateSelectedImage(image: UIImage) -> ImageUploadInfo? { + if let pngData = image.pngData() { + return .init(data: pngData, ext: "PNG") + } else if let jpegData = image.jpegData(compressionQuality: 1) { + return .init(data: jpegData, ext: "JPEG") + } + return nil } } @@ -155,19 +240,13 @@ public extension CenterProfileViewModel { class Input: CenterProfileInputable { - // 서버에서 받아오는데이터 - public var centerName = BehaviorRelay(value: "") - public var centerLocation = BehaviorRelay(value: "") - public var centerPhoneNumber = BehaviorRelay(value: "") - public var centerIntroduction = BehaviorRelay(value: "") - public var centerImage = BehaviorRelay(value: .init()) - // ViewController에서 받아오는 데이터 + public var readyToFetch: PublishRelay = .init() public var editingButtonPressed: PublishRelay = .init() public var editingFinishButtonPressed: PublishRelay = .init() public var editingPhoneNumber: BehaviorRelay = .init(value: "") public var editingInstruction: BehaviorRelay = .init(value: "") - public var editingImage: BehaviorRelay = .init(value: .init()) + public var selectedImage: PublishRelay = .init() } class Output: CenterProfileOutputable { @@ -176,7 +255,7 @@ public extension CenterProfileViewModel { public var centerLocation: Driver public var centerPhoneNumber: Driver public var centerIntroduction: Driver - public var centerImage: Driver + public var displayingImage: Driver // 수정 상태 여부 public var isEditingMode: Driver @@ -186,12 +265,12 @@ public extension CenterProfileViewModel { public var alert: Driver - init(centerName: Driver, centerLocation: Driver, centerPhoneNumber: Driver, centerIntroduction: Driver, centerImage: Driver, isEditingMode: Driver, editingValidation: Driver, alert: Driver) { + init(centerName: Driver, centerLocation: Driver, centerPhoneNumber: Driver, centerIntroduction: Driver, displayingImage: Driver, isEditingMode: Driver, editingValidation: Driver, alert: Driver) { self.centerName = centerName self.centerLocation = centerLocation self.centerPhoneNumber = centerPhoneNumber self.centerIntroduction = centerIntroduction - self.centerImage = centerImage + self.displayingImage = displayingImage self.isEditingMode = isEditingMode self.editingValidation = editingValidation self.alert = alert diff --git a/project/Projects/Presentation/Feature/Worker/ExampleApp/Sources/SceneDelegate.swift b/project/Projects/Presentation/Feature/Worker/ExampleApp/Sources/SceneDelegate.swift index 015452b5..df94dbb6 100644 --- a/project/Projects/Presentation/Feature/Worker/ExampleApp/Sources/SceneDelegate.swift +++ b/project/Projects/Presentation/Feature/Worker/ExampleApp/Sources/SceneDelegate.swift @@ -16,8 +16,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { guard let windowScene = scene as? UIWindowScene else { return } - window = UIWindow(windowScene: windowScene) - window?.rootViewController = ViewController() window?.makeKeyAndVisible() } } diff --git a/project/Projects/Presentation/Feature/Worker/Project.swift b/project/Projects/Presentation/Feature/Worker/Project.swift index a21d9bfe..67367400 100644 --- a/project/Projects/Presentation/Feature/Worker/Project.swift +++ b/project/Projects/Presentation/Feature/Worker/Project.swift @@ -56,6 +56,10 @@ let project = Project( resources: ["ExampleApp/Resources/**"], dependencies: [ .target(name: "WorkerFeature"), + + D.Domain.ConcreteUseCase, + D.Data.ConcreteRepository, + D.Data.NetworkDataSource, ], settings: .settings( configurations: IdleConfiguration.presentationConfigurations diff --git a/project/Projects/Presentation/PresentationCore/Sources/Extensions/Rx+UIViewController.swift b/project/Projects/Presentation/PresentationCore/Sources/Extensions/Rx+UIViewController.swift new file mode 100644 index 00000000..335c3c12 --- /dev/null +++ b/project/Projects/Presentation/PresentationCore/Sources/Extensions/Rx+UIViewController.swift @@ -0,0 +1,17 @@ +// +// Rx+UIViewController.swift +// PresentationCore +// +// Created by choijunios on 7/20/24. +// + +import UIKit +import RxSwift +import RxCocoa + +public extension Reactive where Base: UIViewController { + var viewDidLoad: ControlEvent { + let source = self.methodInvoked(#selector(Base.viewDidLoad)).map { _ in } + return ControlEvent(events: source) + } +}