diff --git a/WAL/WAL.xcodeproj/project.pbxproj b/WAL/WAL.xcodeproj/project.pbxproj index 38d3b0a3..028f2e0e 100644 --- a/WAL/WAL.xcodeproj/project.pbxproj +++ b/WAL/WAL.xcodeproj/project.pbxproj @@ -97,6 +97,7 @@ 9213AD042865A93A009B6002 /* HistoryResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9213AD032865A93A009B6002 /* HistoryResponse.swift */; }; 9213AD072865AB67009B6002 /* HistoryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9213AD062865AB67009B6002 /* HistoryService.swift */; }; 9213AD0A2865ADBC009B6002 /* HistoryAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9213AD092865ADBC009B6002 /* HistoryAPI.swift */; }; + 921D34DD2AF6472A00419B3E /* WalCreatorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 921D34DC2AF6472A00419B3E /* WalCreatorViewModel.swift */; }; 924B60FD28A7D1E4002804B1 /* WalStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 924B60FC28A7D1E4002804B1 /* WalStatus.swift */; }; 925D13F929F95FB00024F0E5 /* BaseResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 925D13F829F95FB00024F0E5 /* BaseResponse.swift */; }; 925D13FB29FABF220024F0E5 /* WalCategoryType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 925D13FA29FABF220024F0E5 /* WalCategoryType.swift */; }; @@ -105,6 +106,7 @@ 926480FF2A34BB91007DEC0B /* CustomIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 926480FE2A34BB91007DEC0B /* CustomIndicator.swift */; }; 926A2A132A02A85F005DDCEA /* MainSubtitle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 926A2A122A02A85F005DDCEA /* MainSubtitle.swift */; }; 926A2A152A054BF9005DDCEA /* MainSubtitleStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 926A2A142A054BF9005DDCEA /* MainSubtitleStatus.swift */; }; + 926E1BBB2AF77761003BE5AF /* WalCreatorRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 926E1BBA2AF77761003BE5AF /* WalCreatorRequest.swift */; }; 927519AF29815A6B0010022A /* MainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 927519AE29815A6B0010022A /* MainViewModel.swift */; }; 927D10EE28210F81001AEB78 /* HistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 927D10ED28210F81001AEB78 /* HistoryViewController.swift */; }; 927D10F028210FD1001AEB78 /* HistoryReserveHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 927D10EF28210FD1001AEB78 /* HistoryReserveHeaderView.swift */; }; @@ -112,6 +114,7 @@ 927D10F428211017001AEB78 /* HistoryDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 927D10F328211017001AEB78 /* HistoryDataModel.swift */; }; 927D10F6282110AA001AEB78 /* HistoryTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 927D10F5282110AA001AEB78 /* HistoryTableViewCell.swift */; }; 929756A9285A4C4600A9FFA8 /* MainContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 929756A8285A4C4600A9FFA8 /* MainContentView.swift */; }; + 92AD24412AA72CAF0075B48E /* WalCreatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92AD24402AA72CAF0075B48E /* WalCreatorViewController.swift */; }; 92B152D02954569800918A3B /* MainTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92B152CF2954569800918A3B /* MainTitleView.swift */; }; 92C1B84E28633FB500F97D75 /* mintPaw.json in Resources */ = {isa = PBXBuildFile; fileRef = 92C1B84C28633FB500F97D75 /* mintPaw.json */; }; 92C1B84F28633FB500F97D75 /* orangePaw.json in Resources */ = {isa = PBXBuildFile; fileRef = 92C1B84D28633FB500F97D75 /* orangePaw.json */; }; @@ -225,6 +228,7 @@ 9213AD032865A93A009B6002 /* HistoryResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryResponse.swift; sourceTree = ""; }; 9213AD062865AB67009B6002 /* HistoryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryService.swift; sourceTree = ""; }; 9213AD092865ADBC009B6002 /* HistoryAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryAPI.swift; sourceTree = ""; }; + 921D34DC2AF6472A00419B3E /* WalCreatorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalCreatorViewModel.swift; sourceTree = ""; }; 92322AAD28049C2C002E281F /* WAL.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WAL.entitlements; sourceTree = ""; }; 924B60FC28A7D1E4002804B1 /* WalStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalStatus.swift; sourceTree = ""; }; 925D13F829F95FB00024F0E5 /* BaseResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseResponse.swift; sourceTree = ""; }; @@ -234,6 +238,7 @@ 926480FE2A34BB91007DEC0B /* CustomIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomIndicator.swift; sourceTree = ""; }; 926A2A122A02A85F005DDCEA /* MainSubtitle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSubtitle.swift; sourceTree = ""; }; 926A2A142A054BF9005DDCEA /* MainSubtitleStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSubtitleStatus.swift; sourceTree = ""; }; + 926E1BBA2AF77761003BE5AF /* WalCreatorRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalCreatorRequest.swift; sourceTree = ""; }; 927519AE29815A6B0010022A /* MainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewModel.swift; sourceTree = ""; }; 927D10ED28210F81001AEB78 /* HistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryViewController.swift; sourceTree = ""; }; 927D10EF28210FD1001AEB78 /* HistoryReserveHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryReserveHeaderView.swift; sourceTree = ""; }; @@ -241,6 +246,7 @@ 927D10F328211017001AEB78 /* HistoryDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryDataModel.swift; sourceTree = ""; }; 927D10F5282110AA001AEB78 /* HistoryTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryTableViewCell.swift; sourceTree = ""; }; 929756A8285A4C4600A9FFA8 /* MainContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainContentView.swift; sourceTree = ""; }; + 92AD24402AA72CAF0075B48E /* WalCreatorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalCreatorViewController.swift; sourceTree = ""; }; 92B152CF2954569800918A3B /* MainTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTitleView.swift; sourceTree = ""; }; 92C1B84C28633FB500F97D75 /* mintPaw.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = mintPaw.json; sourceTree = ""; }; 92C1B84D28633FB500F97D75 /* orangePaw.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = orangePaw.json; sourceTree = ""; }; @@ -375,6 +381,7 @@ 5B44E58528A6C846000ABF0C /* UserAlarm.swift */, 5B44E58728A6C881000ABF0C /* UserCategory.swift */, 5B44E58928A6D044000ABF0C /* UserRequest.swift */, + 926E1BBA2AF77761003BE5AF /* WalCreatorRequest.swift */, ); path = Setting; sourceTree = ""; @@ -693,6 +700,7 @@ 5BD17417282CDD0200C77A95 /* Model */, 5BD17414282CDCCD00C77A95 /* View */, 5BD1741A282CDD1C00C77A95 /* Controller */, + 921D34DB2AF6470B00419B3E /* ViewModel */, ); path = Setting; sourceTree = ""; @@ -725,6 +733,7 @@ 5BC2DD032868DCCE00AFCC29 /* SettingCategoryViewController.swift */, 5BD3585F289D45D60066C804 /* ZanzanbariViewController.swift */, 5BD35862289D45F30066C804 /* SubController */, + 92AD24402AA72CAF0075B48E /* WalCreatorViewController.swift */, ); path = Controller; sourceTree = ""; @@ -792,6 +801,14 @@ path = History; sourceTree = ""; }; + 921D34DB2AF6470B00419B3E /* ViewModel */ = { + isa = PBXGroup; + children = ( + 921D34DC2AF6472A00419B3E /* WalCreatorViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; 925D13F729F95F920024F0E5 /* Protocol */ = { isa = PBXGroup; children = ( @@ -1092,6 +1109,7 @@ 5BA3896727F6C28C0010EAA1 /* AuthService.swift in Sources */, 5BC2DD042868DCCE00AFCC29 /* SettingCategoryViewController.swift in Sources */, 925D13FD29FABF360024F0E5 /* AlarmTimeType.swift in Sources */, + 921D34DD2AF6472A00419B3E /* WalCreatorViewModel.swift in Sources */, 5BC2DD0D2868E75100AFCC29 /* ResignViewController.swift in Sources */, 5B44E57F28A6BCED000ABF0C /* SettingService.swift in Sources */, 927519AF29815A6B0010022A /* MainViewModel.swift in Sources */, @@ -1142,12 +1160,14 @@ 5BECCA8F286A43B800DD70A3 /* MenuButton.swift in Sources */, 925D13FF29FAC29B0024F0E5 /* UICollectionViewCell+.swift in Sources */, 5B422BA228676CF8001B6807 /* (null) in Sources */, + 926E1BBB2AF77761003BE5AF /* WalCreatorRequest.swift in Sources */, 5B44E58A28A6D044000ABF0C /* UserRequest.swift in Sources */, 5BB6616828285A5B00D54353 /* TimeButton.swift in Sources */, 5BD17419282CDD0A00C77A95 /* Setting.swift in Sources */, 5BB3FB792857254200A8A70D /* Constant.swift in Sources */, 5B515DA52A07FBA50013F03B /* Interceptor.swift in Sources */, 5BB17B3A2865BB3000029824 /* OnboardAPI.swift in Sources */, + 92AD24412AA72CAF0075B48E /* WalCreatorViewController.swift in Sources */, 5B7EE87E2822DCB5007BC265 /* MoyaLoggerPlugin.swift in Sources */, 5B020D3F281F31B00057F9B7 /* AlarmCollectionViewCell.swift in Sources */, 9213AD072865AB67009B6002 /* HistoryService.swift in Sources */, diff --git a/WAL/WAL.xcworkspace/xcshareddata/swiftpm/Package.resolved b/WAL/WAL.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2588c915..7aa1a7d7 100644 --- a/WAL/WAL.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/WAL/WAL.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -9,6 +9,15 @@ "version" : "3.3.1" } }, + { + "identity" : "lottie-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/airbnb/lottie-ios.git", + "state" : { + "revision" : "7fe8b6f697ae7db4bf0df270119592cb5d502848", + "version" : "4.4.1" + } + }, { "identity" : "snapkit", "kind" : "remoteSourceControl", @@ -24,7 +33,7 @@ "location" : "https://github.com/zanzanbari/WALKit.git", "state" : { "branch" : "develop", - "revision" : "31eaa8614c9e330f7e0c54d54e95aed1fc66e536" + "revision" : "c976c62e4986561c2eac9080cabfa3aee9dd7f97" } } ], diff --git a/WAL/WAL/Global/Extension/NSNotification.Name+.swift b/WAL/WAL/Global/Extension/NSNotification.Name+.swift index d1681841..8847ef91 100644 --- a/WAL/WAL/Global/Extension/NSNotification.Name+.swift +++ b/WAL/WAL/Global/Extension/NSNotification.Name+.swift @@ -13,4 +13,7 @@ extension NSNotification.Name { static let renewToken = Notification.Name("renewToken") static let enterMain = NSNotification.Name("EnterMain") + + static let walTextViewTapped = NSNotification.Name("WalTextViewTapped") + static let walTextViewReturn = NSNotification.Name("WalTextViewReturn") } diff --git a/WAL/WAL/Network/API/Setting/SettingAPI.swift b/WAL/WAL/Network/API/Setting/SettingAPI.swift index 9aac9d30..fffa2308 100644 --- a/WAL/WAL/Network/API/Setting/SettingAPI.swift +++ b/WAL/WAL/Network/API/Setting/SettingAPI.swift @@ -25,8 +25,7 @@ final class SettingAPI { private(set) var alarm: UserAlarm? private(set) var category: UserCategory? - // MARK: - GET 유저 닉네임 조회하기 - + /// 닉네임 조회하기 (GET) func getUserInfo(completion: @escaping completion) { settingProvider.request(.checkNickname) { [weak self] result in guard let self else { return } @@ -47,8 +46,7 @@ final class SettingAPI { } } - // MARK: - GET 알림 시간 조회하기 - + /// 알림 시간 조회하기 (GET) func getAlarm(completion: @escaping alarmCompletion) { settingProvider.request(.checkAlarm) { [weak self] result in guard let self else { return } @@ -69,8 +67,7 @@ final class SettingAPI { } } - // MARK: - GET 카테고리 조회하기 - + /// 카테고리 조회하기 (GET) func getCategory(completion: @escaping categoryCompletion) { settingProvider.request(.checkCategory) { result in switch result { @@ -90,8 +87,7 @@ final class SettingAPI { } } - // MARK: - POST 유저 닉네임 수정하기 - + /// 유저 닉네임 수정하기 (POST) func postUserInfo(nickname: String, completion: @escaping completion) { settingProvider.request(.editNickname(nickname)) { [weak self] result in guard let self else { return } @@ -106,8 +102,7 @@ final class SettingAPI { } } - // MARK: - POST 알림 시간 수정하기 - + /// 알림 시간 수정하기 (POST) func postAlarm(data: [String], completion: @escaping defaultCompletion) { let param = UserAlarmRequest(timeTypes: data) @@ -124,8 +119,7 @@ final class SettingAPI { } } - // MARK: - POST 카테고리 수정하기 - + /// 카테고리 수정하기 (POST) func postCategory(data: [String], completion: @escaping categoryCompletion) { let param = UserCategoryRequest(categoryTypes: data) @@ -141,4 +135,18 @@ final class SettingAPI { } } } + + /// 왈소리 만들기 (POST) + func postWal(param: WalCreatorRequest, completion: @escaping defaultCompletion) { + settingProvider.request(.walCreator(param)) { result in + switch result { + case .success(let response): + completion(nil, response.statusCode) + case .failure(let error): + print("[왈소리 만들기] DEBUG: - \(error.localizedDescription)") + completion(nil, error.response?.statusCode) + } + } + } + } diff --git a/WAL/WAL/Network/DataModel/Setting/WalCreatorRequest.swift b/WAL/WAL/Network/DataModel/Setting/WalCreatorRequest.swift new file mode 100644 index 00000000..8312cc1e --- /dev/null +++ b/WAL/WAL/Network/DataModel/Setting/WalCreatorRequest.swift @@ -0,0 +1,13 @@ +// +// WalCreatorRequest.swift +// WAL +// +// Created by 소연 on 2023/11/05. +// + +import Foundation + +struct WalCreatorRequest: Codable { + let categoryType: String + let contents: String +} diff --git a/WAL/WAL/Network/Service/Setting/SettingService.swift b/WAL/WAL/Network/Service/Setting/SettingService.swift index 6b2793a1..dbb30f48 100644 --- a/WAL/WAL/Network/Service/Setting/SettingService.swift +++ b/WAL/WAL/Network/Service/Setting/SettingService.swift @@ -14,6 +14,7 @@ enum SettingService { case editNickname(String) case editAlarm(UserAlarmRequest) case editCategory(UserCategoryRequest) + case walCreator(WalCreatorRequest) } extension SettingService: BaseTargetType { @@ -26,6 +27,7 @@ extension SettingService: BaseTargetType { case .editNickname: return "/user/me/nickname/edit" case .editAlarm: return "/onboard/time/edit" case .editCategory: return "/onboard/category/edit" + case .walCreator: return "/censor/create" } } @@ -33,7 +35,7 @@ extension SettingService: BaseTargetType { switch self { case .checkNickname, .checkAlarm, .checkCategory: return .get case .editNickname: return .patch - case .editAlarm, .editCategory: return .post + case .editAlarm, .editCategory, .walCreator: return .post } } @@ -47,6 +49,8 @@ extension SettingService: BaseTargetType { return .requestJSONEncodable(param) case .editCategory(let param): return .requestJSONEncodable(param) + case .walCreator(let param): + return .requestJSONEncodable(param) } } } diff --git a/WAL/WAL/Screen/Create/Controller/CreateViewController.swift b/WAL/WAL/Screen/Create/Controller/CreateViewController.swift index a76009ea..eb53be88 100644 --- a/WAL/WAL/Screen/Create/Controller/CreateViewController.swift +++ b/WAL/WAL/Screen/Create/Controller/CreateViewController.swift @@ -282,10 +282,10 @@ class CreateViewController: UIViewController { //MARK: - CustomMethod private func setSendButton() { - let isCotentFull = walSoundTextView.text.count > 0 && datePickerData.date != nil && datePickerData.time != nil + let isContentFull = walSoundTextView.text.count > 0 && datePickerData.date != nil && datePickerData.time != nil - sendButton.backgroundColor = isCotentFull ? .orange100 : .gray400 - sendButton.isEnabled = isCotentFull + sendButton.backgroundColor = isContentFull ? .orange100 : .gray400 + sendButton.isEnabled = isContentFull } private func scroll(_ datePickerType: DatePickerType) { diff --git a/WAL/WAL/Screen/History/View/HistoryTableViewCell.swift b/WAL/WAL/Screen/History/View/HistoryTableViewCell.swift index 2b1b7a01..86a8fa98 100644 --- a/WAL/WAL/Screen/History/View/HistoryTableViewCell.swift +++ b/WAL/WAL/Screen/History/View/HistoryTableViewCell.swift @@ -57,7 +57,7 @@ class HistoryTableViewCell: UITableViewCell { var dDayLabel = UILabel().then { $0.text = "D-6" - $0.font = WALFont.body8.font + $0.font = WALFont.body10.font $0.textColor = .mint100 } diff --git a/WAL/WAL/Screen/Setting/Controller/SettingViewController.swift b/WAL/WAL/Screen/Setting/Controller/SettingViewController.swift index a5577f05..f5efe291 100644 --- a/WAL/WAL/Screen/Setting/Controller/SettingViewController.swift +++ b/WAL/WAL/Screen/Setting/Controller/SettingViewController.swift @@ -39,6 +39,11 @@ final class SettingViewController: UIViewController, SendNicknameDelegate { // MARK: - Life Cycle + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + configNavigationUI() + } + override func viewDidLoad() { super.viewDidLoad() configUI() @@ -49,12 +54,16 @@ final class SettingViewController: UIViewController, SendNicknameDelegate { // MARK: - InitUI + private func configNavigationUI() { + navigationController?.navigationBar.isHidden = true + } + private func configUI() { view.backgroundColor = .white100 } private func setupLayout() { - view.addSubviews([navigationBar, tableView, backView]) + view.addSubviews([navigationBar, backView, tableView]) navigationBar.snp.makeConstraints { make in make.top.equalTo(view.safeAreaLayoutGuide) @@ -113,9 +122,8 @@ extension SettingViewController: UITableViewDelegate { transition(viewController) } case 2: - print("여기 주석 처리 해제해주세요~ SettingVC 116번 줄") -// let viewController = WalCreatorViewController() -// transition(viewController) + let viewController = WalCreatorViewController() + transition(viewController) default: if indexPath.row == 0 { let viewController = ZanzanbariViewController() diff --git a/WAL/WAL/Screen/Setting/Controller/WalCreatorViewController.swift b/WAL/WAL/Screen/Setting/Controller/WalCreatorViewController.swift new file mode 100644 index 00000000..94259d01 --- /dev/null +++ b/WAL/WAL/Screen/Setting/Controller/WalCreatorViewController.swift @@ -0,0 +1,520 @@ +// +// WalCreatorViewController.swift +// WAL +// +// Created by 소연 on 2023/09/05. +// + +import UIKit + +import RxCocoa +import RxSwift +import Then +import WALKit + +final class WalCreatorViewController: UIViewController { + + // MARK: - UI Property + + private lazy var navigationBar = WALNavigationBar(title: "왈소리 크리에이터").then { + $0.backgroundColor = .white100 + $0.leftIcon = WALIcon.btnBack.image + } + + private lazy var navigationBackView = UIView().then { + $0.backgroundColor = .white100 + } + + /* + 상단 설명 화면 + - 왈뿡이 이미지 + - 타이틀 + - 서브타이틀 + */ + private lazy var guideBackView = UIView().then { + $0.backgroundColor = .white100 + } + + private lazy var guideImageView = UIImageView().then { + $0.image = WALIcon.icnCreate.image + $0.contentMode = .scaleToFill + } + + private lazy var guideTitleLabel = UILabel().then { + $0.textColor = .black100 + $0.font = WALFont.title05.font + $0.text = """ + 당신의 왈소리를 + 세상에 뽐내보세요 + """ + $0.textAlignment = .center + $0.numberOfLines = 0 + } + + private lazy var guideSubtitleLabel = UILabel().then { + $0.textColor = .black100 + $0.font = WALFont.body9.font + $0.text = """ + 하지만 주의하세요, 이 왈소리는 언제, 누구에게 도착할지 + 아무도 몰라요! 그때까지 긴장을 늦추지 마세요. + """ + $0.textAlignment = .center + $0.numberOfLines = 0 + } + + /* + 하단 인풋 화면 + - 왈소리 유형 + - 왈소리 작성 + */ + private lazy var walInputBackView = UIView().then { + $0.backgroundColor = .gray600 + } + + private lazy var walTypeTitleLabel = UILabel().then { + $0.textColor = .black100 + $0.font = WALFont.body4.font + $0.text = "내가 뽐낼 왈소리는 어떤 유형인가요?" + } + + private lazy var walTypeTextField = WALTextField().then { + $0.placeholder = "드립" + } + + private lazy var walTextTitleLabel = UILabel().then { + $0.textColor = .black100 + $0.font = WALFont.body4.font + $0.text = "왈소리를 뽐내주세요" + } + + private lazy var walTextView = UITextView().then { + $0.font = WALFont.body5.font + $0.textColor = .black100 + $0.backgroundColor = .white100 + $0.textContainerInset = UIEdgeInsets(top: 14, left: 16, bottom: 16, right: 16) + $0.isScrollEnabled = false + $0.layer.borderColor = UIColor.gray400.cgColor + $0.layer.borderWidth = 1 + $0.layer.cornerRadius = 10 + $0.delegate = self + $0.tintColor = .orange100 + } + + private lazy var placeholderLabel = UILabel().then { + $0.textColor = .gray300 + $0.font = WALFont.body5.font + $0.text = "내가 받을 왈소리를 작성해주세요" + } + + private lazy var countLabel = UILabel().then { + $0.textColor = .gray200 + $0.font = WALFont.body8.font + $0.text = "0" + } + + private lazy var maximumCountLabel = UILabel().then { + $0.textColor = .gray200 + $0.font = WALFont.body8.font + $0.text = "/50" + } + + private lazy var countStackView = UIStackView().then { + $0.axis = .horizontal + $0.alignment = .fill + $0.distribution = .fill + $0.spacing = 0 + $0.addArrangedSubviews([countLabel, maximumCountLabel]) + } + + private lazy var sendButton = WALPlainButton().then { + $0.title = "보내기" + $0.isDisabled = true + } + + // 로딩 뷰 + private let loadingView = LoadingView().then { + $0.alpha = 0 + } + + private lazy var walTypePickerView = UIPickerView().then { + $0.delegate = self + $0.dataSource = self + $0.autoresizingMask = .flexibleWidth + $0.backgroundColor = .gray500 + } + + // MARK: - Property + + private var walType: [WalCategoryType] = [.comedy, .fuss, .comfort, .yell] + private var selectedWalType: WalCategoryType = .comedy + + private let viewModel = WalCreatorViewModel() + private let disposeBag = DisposeBag() + + // MARK: - Initializer + + init() { + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + + } + + override func viewDidLoad() { + super.viewDidLoad() + + configUI() + setupLayout() + setTextField() + setToolbar() + + rxBindOutput() + rxBindView() + } + + // MARK: - Init UI + + private func configUI() { + view.backgroundColor = .gray600 + + navigationController?.navigationBar.isHidden = true + navigationController?.interactivePopGestureRecognizer?.delegate = nil + + walTextView.returnKeyType = .done + + guideTitleLabel.addLineSpacing(spacing: 0.4) + guideTitleLabel.addLetterSpacing() + guideSubtitleLabel.addLineSpacing(spacing: 0.4) + guideSubtitleLabel.addLetterSpacing() + } + + private func setupLayout() { + view.addSubviews([guideBackView, + navigationBackView, + navigationBar, + walInputBackView, + sendButton]) + + guideBackView.snp.makeConstraints { + $0.bottom.equalTo(walInputBackView.snp.top) + $0.horizontalEdges.equalToSuperview() + $0.height.equalTo(389) + } + navigationBackView.snp.makeConstraints { + $0.top.horizontalEdges.equalToSuperview() + $0.height.equalTo(91) + } + navigationBar.snp.makeConstraints { + $0.top.trailing.equalTo(view.layoutMarginsGuide) + $0.leading.equalToSuperview().inset(4) + } + walInputBackView.snp.makeConstraints { + $0.top.equalTo(navigationBar.snp.bottom).offset(298) + $0.horizontalEdges.equalToSuperview() + $0.bottom.equalTo(sendButton.snp.top) + } + sendButton.snp.makeConstraints { + $0.horizontalEdges.equalToSuperview().inset(Layouts.horizontalMargin) + } + + setupGuideViewLayout() + setupInputViewLayout() + + NSLayoutConstraint.activate([ + view.keyboardLayoutGuide.topAnchor.constraint( + equalTo: sendButton.bottomAnchor, + constant: 16 + ) + ]) + } + + /// 왈소리 크리에이터 Guide UI Layout + private func setupGuideViewLayout() { + guideBackView.addSubviews([guideImageView, + guideTitleLabel, + guideSubtitleLabel]) + + guideImageView.snp.makeConstraints { + $0.top.equalToSuperview().inset(110) + $0.width.equalTo(188) + $0.height.equalTo(122) + $0.centerX.equalToSuperview() + } + + guideTitleLabel.snp.makeConstraints { + $0.top.equalTo(guideImageView.snp.bottom).offset(19) + $0.centerX.equalToSuperview() + } + + guideSubtitleLabel.snp.makeConstraints { + $0.top.equalTo(guideTitleLabel.snp.bottom).offset(11) + $0.horizontalEdges.equalToSuperview().inset(Layouts.horizontalMargin) + } + } + + /// 왈소리 유형/컨텐츠 입력 UI Layout + private func setupInputViewLayout() { + walInputBackView.addSubviews([walTypeTitleLabel, + walTypeTextField, + walTextTitleLabel, + walTextView, + countStackView]) + + walTypeTitleLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(26) + $0.leading.equalToSuperview().inset(Layouts.horizontalMargin) + } + walTypeTextField.snp.makeConstraints { + $0.top.equalTo(walTypeTitleLabel.snp.bottom).offset(17) + $0.horizontalEdges.equalToSuperview().inset(Layouts.horizontalMargin) + } + walTextTitleLabel.snp.makeConstraints { + $0.top.equalTo(walTypeTextField.snp.bottom).offset(20) + $0.leading.equalToSuperview().inset(Layouts.horizontalMargin) + } + walTextView.snp.makeConstraints { + $0.top.equalTo(walTextTitleLabel.snp.bottom).offset(17) + $0.horizontalEdges.equalToSuperview().inset(Layouts.horizontalMargin) + $0.height.equalTo(99) + } + countStackView.snp.makeConstraints { + $0.bottom.equalTo(walTextView.snp.bottom).inset(10) + $0.trailing.equalTo(walTextView.snp.trailing).inset(17) + } + } + + // MARK: - RxBind + + private func rxBindOutput() { + viewModel.output.wal + .asDriver(onErrorDriveWith: .empty()) + .drive(with: self) { owner, result in + switch result { + case .created: + owner.navigationController?.popViewController(animated: true) + default: + owner.navigationController?.popViewController(animated: true) + return + } + } + .disposed(by: disposeBag) + } + + private func rxBindView() { + navigationBar.leftBarButton + .rx + .tap + .preventDuplication() + .asDriver(onErrorDriveWith: .empty()) + .drive(with: self) { owner, _ in + owner.navigationController?.popViewController(animated: true) + } + .disposed(by: disposeBag) + + sendButton + .rx + .tap + .preventDuplication() + .asDriver(onErrorDriveWith: .empty()) + .drive(with: self) { owner, _ in + owner.viewModel.input.postWal.accept((owner.selectedWalType, owner.walTextView.text)) + } + .disposed(by: disposeBag) + } + + private func rxBindInput() { + + } + + // MARK: - Custom Method + + private func configLoadingView() { + let loadingView = LoadingView() + view.addSubview(loadingView) + loadingView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + loadingView.play() + } + + private func setSendButton() { + let isCotentFull = walTextView.text.count > 0 + + sendButton.backgroundColor = isCotentFull ? .orange100 : .gray400 + sendButton.isEnabled = isCotentFull + } + + private func setTextField() { + walTypeTextField.delegate = self + + walTypeTextField.tintColor = .clear + walTypeTextField.inputView = walTypePickerView + walTypeTextField.text = WalCategoryType.comedy.kor + } + + private func setToolbar() { + let toolBar = UIToolbar() + toolBar.sizeToFit() + toolBar.translatesAutoresizingMaskIntoConstraints = false + toolBar.tintColor = .orange100 + + let button = UIBarButtonItem(title: "확인", style: .done, target: self, action: #selector(touchUpDoneButton)) + let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) + + toolBar.setItems([flexibleSpace, button], animated: true) + toolBar.isUserInteractionEnabled = true + + walTypeTextField.inputAccessoryView = toolBar + } + + private func keyboardUp() { + UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut, animations: { + self.walInputBackView.snp.remakeConstraints { + $0.top.equalTo(self.navigationBar.snp.bottom) + $0.horizontalEdges.equalToSuperview() + $0.bottom.equalTo(self.sendButton.snp.top) + } + + self.walInputBackView.superview?.layoutIfNeeded() + }) + } + + private func keyboardDown() { + UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut, animations: { + self.walInputBackView.snp.remakeConstraints { + $0.top.equalTo(self.navigationBar.snp.bottom).offset(298) + $0.horizontalEdges.equalToSuperview() + $0.bottom.equalTo(self.sendButton.snp.top) + } + self.walInputBackView.superview?.layoutIfNeeded() + }) + } + + // MARK: - @objc + + @objc func touchUpDoneButton() { + self.view.endEditing(true) + keyboardDown() + walTypeTextField.layer.borderColor = UIColor.gray400.cgColor + } + +} + +// MARK: - Layouts { + +extension WalCreatorViewController { + + enum Layouts { + /// 좌/우 + static let horizontalMargin = 20 + } + +} + +// MARK: - UITextField Delegate + +extension WalCreatorViewController: UITextFieldDelegate { + + func textFieldDidBeginEditing(_ textField: UITextField) { + keyboardUp() + walTypeTextField.layer.borderColor = UIColor.orange100.cgColor + } + + func textFieldDidEndEditing(_ textField: UITextField) { + keyboardDown() + walTypeTextField.layer.borderColor = UIColor.gray400.cgColor + } + +} + +// MARK: - UITextView Delegate + +extension WalCreatorViewController: UITextViewDelegate { + + func textViewDidEndEditing(_ textView: UITextView) { + walTextView.layer.borderColor = UIColor.gray400.cgColor + + (walTextView.text.count == 0) ? (placeholderLabel.isHidden = false) : (placeholderLabel.isHidden = true) + + keyboardDown() + } + + func textViewDidBeginEditing(_ textView: UITextView) { + walTextView.layer.borderColor = UIColor.orange100.cgColor + placeholderLabel.isHidden = true + + keyboardUp() + } + + func textViewDidChange(_ textView: UITextView) { + countLabel.text = "\(walTextView.text.count)" + + countLabel.textColor = walTextView.text.count >= 50 ? .orange100 : .gray200 + + if walTextView.text.count > 50 { + walTextView.deleteBackward() + } + + setSendButton() + } + + func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { + if text == "\n" { + walTextView.resignFirstResponder() + return false + } + return true + } + +} + +// MARK: - UIPickerView Delegate + +extension WalCreatorViewController: UIPickerViewDelegate, UIPickerViewDataSource { + + func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 1 + } + + func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + return walType.count + } + + func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { + let label = (view as? UILabel) ?? UILabel() + + label.textAlignment = .center + + if pickerView.selectedRow(inComponent: component) == row { + label.attributedText = NSAttributedString(string: walType[row].kor, + attributes: [NSAttributedString.Key.font: WALFont.body2.font, + NSAttributedString.Key.foregroundColor: UIColor.orange100]) + selectedWalType = walType[row] + } else { + label.attributedText = NSAttributedString(string: walType[row].kor, + attributes: [NSAttributedString.Key.font: WALFont.body2.font, + NSAttributedString.Key.foregroundColor: UIColor.gray100]) + } + + return label + } + + func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { + return 44 + } + + func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { + return 200 + } + + func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + walTypeTextField.text = walType[row].kor + pickerView.reloadAllComponents() + } + +} diff --git a/WAL/WAL/Screen/Setting/ViewModel/WalCreatorViewModel.swift b/WAL/WAL/Screen/Setting/ViewModel/WalCreatorViewModel.swift new file mode 100644 index 00000000..bbfcabaa --- /dev/null +++ b/WAL/WAL/Screen/Setting/ViewModel/WalCreatorViewModel.swift @@ -0,0 +1,128 @@ +// +// WalCreatorViewModel.swift +// WAL +// +// Created by 소연 on 2023/11/04. +// + +import Foundation + +import RxSwift +import RxCocoa + +final class WalCreatorViewModel { + + // MARK: - Essential + + private(set) var input = Input() + private(set) var output = Output() + private(set) var bothways = Bothways() + private(set) var dependency = Dependency() + // private(set) var load: Load // info, load, initialize + + // MARK: - Properties + + private let disposeBag = DisposeBag() + + // MARK: - Life Cycle + + init() { + rxBind() + } + + // init(load: Load) { + // self.load = load + // + // rxBind() + // } + + // MARK: - Helpers + + private func rxBind() { + input.postWal + .bind(with: self) { owner, args in + let (type, contents) = args + owner.postWal(type: type, contents: contents) + } + .disposed(by: disposeBag) + } + +} + +// MARK: - API Request + +extension WalCreatorViewModel { + + /// 왈소리 크리에이터 만들기 + private func postWal(type: WalCategoryType, contents: String) { + let param: WalCreatorRequest = .init(categoryType: type.rawValue, contents: contents) + SettingAPI.shared.postWal(param: param) { [weak self] response, statusCode in + guard let self else { return } + guard let statusCode = statusCode else { return } + + let networkResult = NetworkResult(rawValue: statusCode) ?? .none + switch networkResult { + case .created: + self.output.wal.accept(.created) + case .unAuthorized: + self.requestRefreshToken(requestType: param) + default: + self.output.wal.accept(networkResult) + return + } + } + } + + /// 토근 재발급 + private func requestRefreshToken(requestType: WalCreatorRequest) { + AuthAPI.shared.postReissue { [weak self] response, statusCode in + guard let _self = self else { return } + guard let _statusCode = statusCode else { return } + + let networkResult = NetworkResult(rawValue: _statusCode) ?? .none + switch networkResult { + case .okay: + print("왈소리 만들기 API 한번 더 연결") + case .unAuthorized: + print("로그인 화면으로 이동") + default: + break + } + } + } + +} + +// MARK: - ViewModel Structure + +extension WalCreatorViewModel { + + enum ErrorResult: Error { + case postWal + } + + struct Input { + let postWal = PublishRelay<(WalCategoryType, String)>() + } + + struct Output { + let wal = PublishRelay() + } + + /// View <-> ViewModel + struct Bothways { + let userInteraction = PublishRelay() + let loading = PublishRelay() + } + + /// Service + struct Dependency { + + } + + /// Initialze (생성자로 enum 을 필요로 할 경우 해당 enum은 전역으로 선언) + // struct Load { + // + // } + +}