diff --git a/SOPT-Stamp-iOS/SOPT-iOS.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SOPT-Stamp-iOS/SOPT-iOS.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d98100..00000000 --- a/SOPT-Stamp-iOS/SOPT-iOS.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/SOPT-iOS/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlists.swift b/SOPT-iOS/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlists.swift index 4b94a2aa..a19e8812 100644 --- a/SOPT-iOS/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlists.swift +++ b/SOPT-iOS/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlists.swift @@ -34,7 +34,7 @@ public extension Project { "App Transport Security Settings": ["Allow Arbitrary Loads": true], "NSAppTransportSecurity": ["NSAllowsArbitraryLoads": true], "ITSAppUsesNonExemptEncryption": false, - "UIUserInterfaceStyle": "Light", + "UIUserInterfaceStyle": "Dark", "NSPhotoLibraryUsageDescription": "미션과 관련된 사진을 업로드하기 위해 갤러리 권한이 필요합니다.", "CFBundleURLTypes": [ [ diff --git a/SOPT-iOS/Plugins/EnvPlugin/ProjectDescriptionHelpers/SettingsDictionary+.swift b/SOPT-iOS/Plugins/EnvPlugin/ProjectDescriptionHelpers/SettingsDictionary+.swift index e707153d..0a0a62d1 100644 --- a/SOPT-iOS/Plugins/EnvPlugin/ProjectDescriptionHelpers/SettingsDictionary+.swift +++ b/SOPT-iOS/Plugins/EnvPlugin/ProjectDescriptionHelpers/SettingsDictionary+.swift @@ -17,7 +17,7 @@ public extension SettingsDictionary { static let baseSettings: Self = [ "OTHER_LDFLAGS" : [ - "-Xlinker -interposable" + "$(inherited)" ] ] diff --git a/SOPT-iOS/Projects/Core/Sources/Enum/AttendanceStateType.swift b/SOPT-iOS/Projects/Core/Sources/Enum/AttendanceStateType.swift index b1514218..d6dc6ff0 100644 --- a/SOPT-iOS/Projects/Core/Sources/Enum/AttendanceStateType.swift +++ b/SOPT-iOS/Projects/Core/Sources/Enum/AttendanceStateType.swift @@ -8,9 +8,18 @@ import Foundation -public enum AttendanceStateType: String, CaseIterable { - case all = "전체" - case attendance = "출석" - case tardy = "지각" - case absent = "결석" +public enum AttendanceStateType: String { + case attendance + case tardy + case absent + case participate + + public var korean: String { + switch self { + case .attendance: return "출석" + case .tardy: return "지각" + case .absent: return "결석" + case .participate: return "참여" + } + } } diff --git a/SOPT-iOS/Projects/Core/Sources/Enum/UserType.swift b/SOPT-iOS/Projects/Core/Sources/Enum/UserType.swift index 2c7d4b85..e23de015 100644 --- a/SOPT-iOS/Projects/Core/Sources/Enum/UserType.swift +++ b/SOPT-iOS/Projects/Core/Sources/Enum/UserType.swift @@ -8,10 +8,11 @@ import Foundation -public enum UserType { - case visitor // 비회원 - case active // 활동 회원 - case inactive // 비활동 회원 +public enum UserType: String { + case visitor = "UNAUTHENTICATED" // 비회원 + case active = "ACTIVE" // 활동 회원 + case inactive = "INACTIVE" // 비활동 회원 + case unregisteredInactive = "UNREGISTERED" // 비활동 회원 + 플그 프로필 미등록 public func makeDescription(recentHistory: Int) -> String { switch self { @@ -21,6 +22,8 @@ public enum UserType { return "\(recentHistory)\(I18N.Main.active)" case .inactive: return "\(recentHistory)\(I18N.Main.inactive)" + case .unregisteredInactive: + return I18N.Main.inactiveMember } } } diff --git a/SOPT-iOS/Projects/Core/Sources/Literals/ExternalURL.swift b/SOPT-iOS/Projects/Core/Sources/Literals/ExternalURL.swift index 1a8cf1f6..65fafa6c 100644 --- a/SOPT-iOS/Projects/Core/Sources/Literals/ExternalURL.swift +++ b/SOPT-iOS/Projects/Core/Sources/Literals/ExternalURL.swift @@ -19,6 +19,10 @@ public struct ExternalURL { public static let findEmail = "https://forms.gle/XkVFMUPsWWV1DXU38" } + public struct SOPT { + public static let project = "https://sopt.org/project" + } + public struct Playground { #if DEV || PROD public static let main = "https://playground.sopt.org" diff --git a/SOPT-iOS/Projects/Core/Sources/Literals/StringLiterals.swift b/SOPT-iOS/Projects/Core/Sources/Literals/StringLiterals.swift index 6a87882c..5f2deedf 100644 --- a/SOPT-iOS/Projects/Core/Sources/Literals/StringLiterals.swift +++ b/SOPT-iOS/Projects/Core/Sources/Literals/StringLiterals.swift @@ -151,6 +151,8 @@ public struct I18N { public static let encourage = "안녕하세요, \nSOPT의 열정이 되어주세요!" public static let hello = "안녕하세요" public static let welcome = "안녕하세요, \nSOPT에 오신 것을 환영합니다!" + public static let failedToGetUserInfo = "활동 정보를 가져올 수 없어요." + public static let needToRegisterPlayground = "플레이그라운드에서 프로필을 업데이트하면\n 서비스를 원활하게 사용할 수 있어요." public static func userHistory(name: String, months: String) -> String { return "\(name) 님은 \nSOPT와 \(months)개월째" diff --git a/SOPT-iOS/Projects/Data/Sources/Repository/ShowAttendanceRepository.swift b/SOPT-iOS/Projects/Data/Sources/Repository/ShowAttendanceRepository.swift index acb372d7..5d84d98b 100644 --- a/SOPT-iOS/Projects/Data/Sources/Repository/ShowAttendanceRepository.swift +++ b/SOPT-iOS/Projects/Data/Sources/Repository/ShowAttendanceRepository.swift @@ -26,49 +26,36 @@ public class ShowAttendanceRepository { extension ShowAttendanceRepository: ShowAttendanceRepositoryInterface { public func fetchAttendanceScheduleModel() -> AnyPublisher { // return Future { promise in -//// promise(.success(AttendanceScheduleModel(type: "HAS_ATTENDANCE", -//// location: "단국대학교 혜당관", -//// name: "3차 세미나", -//// startDate: "2023-04-06T14:14:00", endDate: "2023-04-06T18:00:00", message: "", -//// attendances: [TodayAttendanceModel(status: "ATTENDANCE", attendedAt: "2023-04-13T14:12:09"), -//// TodayAttendanceModel(status: "ABSENT", attendedAt: -//// "2023-04-13T14:10:04")]))) -// // promise(.success(AttendanceScheduleModel(type: "HAS_ATTENDANCE", -// location: "한국대학교 혜당관", -// name: "2차 행사", -// startDate: "2023-04-06T14:14:00", endDate: "2023-04-06T18:00:00", -// message: "행사도 참여하고, 출석점수도 받고, 일석이조!", -// attendances: [TodayAttendanceModel(status: "ATTENDANCE", attendedAt: "2023-04-13T14:12:09"), -// TodayAttendanceModel(status: "ABSENT", attendedAt: "2023-04-13T14:10:04")]))) -// -//// promise(.success(AttendanceScheduleModel(type: "NO_SESSION", -//// location: "", -//// name: "", -//// startDate: "", endDate: "", message: "", -//// attendances: [TodayAttendanceModel(status: "", attendedAt: ""), -//// TodayAttendanceModel(status: "", attendedAt: "")]))) -// } -// .eraseToAnyPublisher() +// location: "솝트대학교 IT 창업관", +// name: "3차 세미나", +// startDate: "2023-04-29T14:00:00", endDate: "2023-04-29T18:00:00", +// message: "", +// attendances: [TodayAttendanceModel(status: "ATTENDANCE", attendedAt: "2023-04-29T14:00:00"), +// TodayAttendanceModel(status: "ABSENT", attendedAt: "2023-04-29T14:02:00")]))) +// }.eraseToAnyPublisher() + return self.attendanceService.fetchAttendanceSchedule() - .compactMap { $0.data?.toDomain() } - .eraseToAnyPublisher() + .compactMap { $0.data?.toDomain() } + .eraseToAnyPublisher() } public func fetchAttendanceScoreModel() -> AnyPublisher { // return Future { promise in -// promise(.success(AttendanceScoreModel.init(part: "iOS", -// generation: 31, -// name: "윤수빈", -// score: 2.0, -// total: TotalScoreModel(attendance: 2, absent: 0, tardy: 1, participate: 1), -// attendances: [AttendanceModel(attribute: "ETC", name: "솝커톤", status: "TARDY", date: "4월 22일"), -// AttendanceModel(attribute: "SEMINAR", name: "iOS 2차 세미나", status: "ATTENDANCE", date: "4월 15일"), -// AttendanceModel(attribute: "SEMINAR", name: "iOS 1차 세미나", status: "ATTENDANCE", date: "4월 8일"), -// AttendanceModel(attribute: "ETC", name: "OT", status: "PARTICIPATE", date: "4월 1일")]))) +// promise(.success(AttendanceScoreModel.init(part: "디자인", +// generation: 32, +// name: "김솝트", +// score: 1.0, +// total: TotalScoreModel(attendance: 2, absent: 1, tardy: 1, participate: 1), +// attendances: [AttendanceModel(attribute: "ETC", name: "솝커톤", status: "PARTICIPATE", date: "4월 22일"), +// AttendanceModel(attribute: "ETC", name: "1차 행사", status: "ATTENDANCE", date: "4월 15일"), +// AttendanceModel(attribute: "SEMINAR", name: "2차 세미나", status: "ATTENDANCE", date: "4월 08일"), +// AttendanceModel(attribute: "SEMINAR", name: "1차 세미나", status: "ABSENT", date: "4월 01일"), +// AttendanceModel(attribute: "ETC", name: "OT", status: "TARDY", date: "3월 25일")]))) // }.eraseToAnyPublisher() + return self.attendanceService.fetchAttendanceScore() - .compactMap{ $0.data?.toDomain() } - .eraseToAnyPublisher() + .compactMap{ $0.data?.toDomain() } + .eraseToAnyPublisher() } } diff --git a/SOPT-iOS/Projects/Data/Sources/Repository/SignInRepository.swift b/SOPT-iOS/Projects/Data/Sources/Repository/SignInRepository.swift index 519cab37..c43fad63 100644 --- a/SOPT-iOS/Projects/Data/Sources/Repository/SignInRepository.swift +++ b/SOPT-iOS/Projects/Data/Sources/Repository/SignInRepository.swift @@ -28,11 +28,18 @@ public class SignInRepository { } extension SignInRepository: SignInRepositoryInterface { - - public func requestSignIn(token: String) -> AnyPublisher { + public func requestSignIn(token: String) -> AnyPublisher { authService.signIn(token: token) - .catch({ _ in - return self.userService.reissuance() + .catch ({ error in + guard + let error = error as? SOPTAPPError, + case .network(let statusCode) = error, + statusCode == 400 + else { + return self.userService.reissuance() + } + + return Fail(error: error).eraseToAnyPublisher() }) .map { entity in UserDefaultKeyList.Auth.appAccessToken = entity.accessToken @@ -43,7 +50,6 @@ extension SignInRepository: SignInRepositoryInterface { : false return true } - .replaceError(with: false) .withUnretained(self) .flatMap { owner, isSuccessed in guard isSuccessed else { diff --git a/SOPT-iOS/Projects/Domain/Sources/Model/UserMainInfoModel.swift b/SOPT-iOS/Projects/Domain/Sources/Model/UserMainInfoModel.swift index cdae37cb..2730bddd 100644 --- a/SOPT-iOS/Projects/Domain/Sources/Model/UserMainInfoModel.swift +++ b/SOPT-iOS/Projects/Domain/Sources/Model/UserMainInfoModel.swift @@ -8,6 +8,8 @@ import Foundation +import Core + public struct UserMainInfoModel { public let status, name: String public let profileImage: String? @@ -17,6 +19,19 @@ public struct UserMainInfoModel { public let responseMessage: String? public var withError: Bool = false + public var userType: UserType { + switch status { + case "": // 플그 미등록인 경우 + return .unregisteredInactive + case UserType.active.rawValue: + return .active + case UserType.inactive.rawValue: + return .inactive + default: + return .visitor + } + } + public init(status: String, name: String, profileImage: String?, historyList: [Int], attendanceScore: Float?, announcement: String?, responseMessage: String?) { self.status = status self.name = name diff --git a/SOPT-iOS/Projects/Domain/Sources/RepositoryInterface/SignInRepositoryInterface.swift b/SOPT-iOS/Projects/Domain/Sources/RepositoryInterface/SignInRepositoryInterface.swift index e822de80..0b7b6a4f 100644 --- a/SOPT-iOS/Projects/Domain/Sources/RepositoryInterface/SignInRepositoryInterface.swift +++ b/SOPT-iOS/Projects/Domain/Sources/RepositoryInterface/SignInRepositoryInterface.swift @@ -11,5 +11,5 @@ import Combine // TODO: - User 유형 설정 방식 생각하기 public protocol SignInRepositoryInterface { - func requestSignIn(token: String) -> AnyPublisher + func requestSignIn(token: String) -> AnyPublisher } diff --git a/SOPT-iOS/Projects/Domain/Sources/UseCase/MainUseCase.swift b/SOPT-iOS/Projects/Domain/Sources/UseCase/MainUseCase.swift index dc61cbd7..61739519 100644 --- a/SOPT-iOS/Projects/Domain/Sources/UseCase/MainUseCase.swift +++ b/SOPT-iOS/Projects/Domain/Sources/UseCase/MainUseCase.swift @@ -37,6 +37,7 @@ extension DefaultMainUseCase: MainUseCase { .sink { event in print("MainUseCase: \(event)") } receiveValue: { [weak self] userMainInfoModel in + self?.setUserType(with: userMainInfoModel?.userType) self?.userMainInfo.send(userMainInfoModel) }.store(in: self.cancelBag) } @@ -49,4 +50,15 @@ extension DefaultMainUseCase: MainUseCase { self?.serviceState.send(serviceStateModel) }.store(in: self.cancelBag) } + + private func setUserType(with userType: UserType?) { + switch userType { + case .none, .unregisteredInactive, .inactive: // nil인 경우도 플그 미등록 유저로 취급 + UserDefaultKeyList.Auth.isActiveUser = false + case .active: + UserDefaultKeyList.Auth.isActiveUser = true + default: + UserDefaultKeyList.Auth.isActiveUser = false + } + } } diff --git a/SOPT-iOS/Projects/Domain/Sources/UseCase/SignInUseCase.swift b/SOPT-iOS/Projects/Domain/Sources/UseCase/SignInUseCase.swift index 8a288798..a6616d07 100644 --- a/SOPT-iOS/Projects/Domain/Sources/UseCase/SignInUseCase.swift +++ b/SOPT-iOS/Projects/Domain/Sources/UseCase/SignInUseCase.swift @@ -10,16 +10,23 @@ import Combine import Core +// NOTE: unregistered 없어지면 당장 삭제할거임 +public enum SiginInHandleableType { + case loginSuccess + case loginFailure + case unregistedProfile +} + public protocol SignInUseCase { func requestSignIn(token: String) - var signInSuccess: CurrentValueSubject { get set } + var signInSuccess: CurrentValueSubject { get set } } public class DefaultSignInUseCase { private let repository: SignInRepositoryInterface private var cancelBag = CancelBag() - public var signInSuccess = CurrentValueSubject(false) + public var signInSuccess = CurrentValueSubject(.loginFailure) public init(repository: SignInRepositoryInterface) { self.repository = repository @@ -29,11 +36,19 @@ public class DefaultSignInUseCase { extension DefaultSignInUseCase: SignInUseCase { public func requestSignIn(token: String) { repository.requestSignIn(token: token) - .replaceError(with: false) .sink { event in - print("SignInUseCase: \(event)") + switch event { + case .failure(let error): + // NOTE: signin에서 400이 날아오는 경우는 현재 기준 한가지 경우. + // 4월 19일 기준으로 대응됨. 만약 다르게 날아오거나 에러스펙을 새로 정의하는 경우 다시 리팩토링 해야 함. + // @승호. 2023 04 19 + + self.signInSuccess.send(.unregistedProfile) + case .finished: + print("SignInUseCase: \(event)") + } } receiveValue: { isSuccessed in - self.signInSuccess.send(isSuccessed) + self.signInSuccess.send(isSuccessed ? .loginSuccess : .loginFailure) }.store(in: self.cancelBag) } } diff --git a/SOPT-iOS/Projects/Features/AppMyPageFeature/Sources/AppMypageScene/AppMyPageViewController.swift b/SOPT-iOS/Projects/Features/AppMyPageFeature/Sources/AppMypageScene/AppMyPageViewController.swift index c8779643..d73b04e5 100644 --- a/SOPT-iOS/Projects/Features/AppMyPageFeature/Sources/AppMypageScene/AppMyPageViewController.swift +++ b/SOPT-iOS/Projects/Features/AppMyPageFeature/Sources/AppMypageScene/AppMyPageViewController.swift @@ -139,6 +139,17 @@ public final class AppMyPageVC: UIViewController, AppMyPageViewControllable { frame: self.view.frame ) + // MARK: For UnregisteredInActive + + private lazy var etcForUnregisteredInActiveSectionGroup = MypageSectionGroupView( + headerTitle: I18N.MyPage.etcSectionGroupTitle, + subviews: [ + self.logoutListItem, + self.withDrawalListItem + ], + frame: self.view.frame + ) + public init( userType: UserType, viewModel: AppMyPageViewModel, @@ -187,6 +198,12 @@ extension AppMyPageVC { self.servicePolicySectionGroup, self.etcForVisitorsSectionGroup ) + + case .unregisteredInactive: + self.contentStackView.addArrangedSubviews( + self.servicePolicySectionGroup, + self.etcForUnregisteredInActiveSectionGroup + ) } } @@ -264,7 +281,9 @@ extension AppMyPageVC { } self.withDrawalListItem.addTapGestureRecognizer { - let viewController = self.factory.makeWithdrawalVC().viewController + let viewController = self.factory.makeWithdrawalVC( + userType: self.userType + ).viewController self.navigationController?.pushViewController(viewController, animated: true) } diff --git a/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/VC/ShowAttendanceVC.swift b/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/VC/ShowAttendanceVC.swift index ad50d2d6..e0eead42 100644 --- a/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/VC/ShowAttendanceVC.swift +++ b/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/VC/ShowAttendanceVC.swift @@ -33,18 +33,22 @@ public final class ShowAttendanceVC: UIViewController, ShowAttendanceViewControl } } - private var viewdidload = PassthroughSubject() - + private var viewWillAppear = PassthroughSubject() + // MARK: - UI Components - private let containerScrollView = UIScrollView() + private lazy var containerScrollView: UIScrollView = { + let sv = UIScrollView() + sv.showsVerticalScrollIndicator = false + sv.refreshControl = refresher + sv.isExclusiveTouch = true + return sv + }() + private let contentView = UIView() - private lazy var navibar = OPNavigationBar(self, type: .bothButtons) + private lazy var navibar = OPNavigationBar(self, type: .oneLeftButton) .addMiddleLabel(title: I18N.Attendance.attendance) - .addRightButtonAction { - self.refreshButtonDidTap() - } private lazy var headerScheduleView: TodayScheduleView = { switch sceneType { @@ -63,6 +67,18 @@ public final class ShowAttendanceVC: UIViewController, ShowAttendanceViewControl return button }() + private let refresher: UIRefreshControl = { + let rf = UIRefreshControl() + rf.tintColor = .gray + return rf + }() + + private lazy var infoButton: UIButton = { + let button = UIButton(type: .system) + button.addTarget(self, action: #selector(infoButtonDidTap), for: .touchUpInside) + return button + }() + // MARK: - Initialization public init(viewModel: ShowAttendanceViewModel, @@ -81,10 +97,14 @@ public final class ShowAttendanceVC: UIViewController, ShowAttendanceViewControl public override func viewDidLoad() { super.viewDidLoad() self.bindViewModels() - self.bindViews() self.setUI() self.setLayout() - self.viewdidload.send(()) + self.swipeRecognizer() + } + + public override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.viewWillAppear.send(()) } } @@ -94,14 +114,15 @@ extension ShowAttendanceVC { private func setUI() { self.navigationController?.navigationBar.isHidden = true - self.view.backgroundColor = .black - containerScrollView.backgroundColor = .black + self.view.backgroundColor = DSKitAsset.Colors.black100.color + containerScrollView.backgroundColor = DSKitAsset.Colors.black100.color } private func setLayout() { view.addSubviews(navibar, containerScrollView, attendanceButton) containerScrollView.addSubview(contentView) contentView.addSubviews(headerScheduleView, attendanceScoreView) + attendanceScoreView.addSubview(infoButton) navibar.snp.makeConstraints { $0.top.leading.trailing.equalTo(view.safeAreaLayoutGuide) @@ -115,6 +136,7 @@ extension ShowAttendanceVC { contentView.snp.makeConstraints { $0.edges.equalToSuperview() + $0.width.equalToSuperview() } headerScheduleView.snp.makeConstraints { @@ -133,6 +155,12 @@ extension ShowAttendanceVC { $0.bottom.equalTo(view.safeAreaLayoutGuide) $0.height.equalTo(56) } + + infoButton.snp.makeConstraints { + $0.top.equalToSuperview().inset(58) + $0.trailing.equalToSuperview().inset(32) + $0.width.height.equalTo(24) + } } } @@ -140,17 +168,12 @@ extension ShowAttendanceVC { extension ShowAttendanceVC { - private func bindViews() { + private func bindViewModels() { - navibar.rightButtonTapped + let viewWillAppear = viewWillAppear.asDriver() + let refreshStarted = refresher.publisher(for: .valueChanged) + .mapVoid() .asDriver() - .withUnretained(self) - .sink { owner, _ in - owner.refreshButtonDidTap() - }.store(in: self.cancelBag) - } - - private func bindViewModels() { attendanceButton.publisher(for: .touchUpInside) .withUnretained(self) @@ -162,8 +185,8 @@ extension ShowAttendanceVC { } .store(in: self.cancelBag) - let input = ShowAttendanceViewModel.Input(viewDidLoad: viewdidload.asDriver(), - refreshButtonTapped: navibar.rightButtonTapped) + let input = ShowAttendanceViewModel.Input(viewWillAppear: viewWillAppear, + refreshStarted: refreshStarted) let output = self.viewModel.transform(from: input, cancelBag: self.cancelBag) output.$scheduleModel @@ -180,19 +203,21 @@ extension ShowAttendanceVC { self.headerScheduleView.updateLayout(.unscheduledDay) self.setAttendanceButton(isEnabled: false) } + self.endRefresh() }) .store(in: self.cancelBag) output.$scoreModel .sink { model in guard let model else { return } + self.infoButton.setImage(DSKitAsset.Assets.opInfo.image, for: .normal) self.setScoreData(model) + self.endRefresh() }.store(in: self.cancelBag) } - @objc - private func refreshButtonDidTap() { - print("refresh button did tap") + private func endRefresh() { + self.refresher.endRefreshing() } private func setScheduledData(_ model: AttendanceScheduleModel) { @@ -209,7 +234,7 @@ extension ShowAttendanceVC { private func setScoreData(_ model: AttendanceScoreModel) { attendanceScoreView.setMyInfoData(name: model.name, part: model.part, generation: model.generation, count: model.score) - attendanceScoreView.setMyTotalScoreData(attendance: model.total.attendance, tardy: model.total.tardy, absent: model.total.absent) + attendanceScoreView.setMyTotalScoreData(attendance: model.total.attendance, tardy: model.total.tardy, absent: model.total.absent, participate: model.total.participate) attendanceScoreView.setMyAttendanceTableData(model.attendances) } @@ -218,4 +243,20 @@ extension ShowAttendanceVC { attendanceButton.isEnabled = isEnabled attendanceButton.setTitle(title, for: .normal) } + + @objc + private func infoButtonDidTap() { + if let url = URL(string: "https://sopt.org/rules") { + UIApplication.shared.open(url) + } + } +} + +// MARK: - UIGestureRecognizerDelegate + +extension ShowAttendanceVC: UIGestureRecognizerDelegate { + + private func swipeRecognizer() { + self.navigationController?.interactivePopGestureRecognizer?.delegate = self + } } diff --git a/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/ViewModel/ShowAttendanceViewModel.swift b/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/ViewModel/ShowAttendanceViewModel.swift index 39467caa..d0638ef6 100644 --- a/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/ViewModel/ShowAttendanceViewModel.swift +++ b/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/ViewModel/ShowAttendanceViewModel.swift @@ -29,8 +29,8 @@ public final class ShowAttendanceViewModel: ViewModelType { // MARK: - Inputs public struct Input { - let viewDidLoad: Driver - let refreshButtonTapped: Driver + let viewWillAppear: Driver + let refreshStarted: Driver } // MARK: - Outputs @@ -54,7 +54,7 @@ extension ShowAttendanceViewModel { self.bindOutput(output: output, cancelBag: cancelBag) - input.viewDidLoad.merge(with: input.refreshButtonTapped) + input.viewWillAppear.merge(with: input.refreshStarted) .withUnretained(self) .sink { owner, _ in owner.useCase.fetchAttendanceSchedule() diff --git a/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/Views/AttendanceScoreComponents/MyAttendanceStateTVC.swift b/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/Views/AttendanceScoreComponents/MyAttendanceStateTVC.swift index 429aadb7..39cf7474 100644 --- a/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/Views/AttendanceScoreComponents/MyAttendanceStateTVC.swift +++ b/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/Views/AttendanceScoreComponents/MyAttendanceStateTVC.swift @@ -82,14 +82,16 @@ extension MyAttendanceStateTVC { extension MyAttendanceStateTVC { func setData(model: AttendanceModel) { - - if model.status == "ATTENDANCE" { + guard let status = AttendanceStateType(rawValue: model.status.lowercased()) else { return } + + switch status { + case .attendance: stateImageView.image = DSKitAsset.Assets.opStateAttendance.image - } else if model.status == "ABSENT" { + case .absent: stateImageView.image = DSKitAsset.Assets.opStateAbsent.image - } else if model.status == "TARDY" { + case .tardy: stateImageView.image = DSKitAsset.Assets.opStateTardy.image - } else { + case .participate: stateImageView.image = DSKitAsset.Assets.opStateParticipate.image } diff --git a/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/Views/AttendanceScoreComponents/MyInformationWithScoreView.swift b/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/Views/AttendanceScoreComponents/MyInformationWithScoreView.swift index c8abddc6..b185f381 100644 --- a/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/Views/AttendanceScoreComponents/MyInformationWithScoreView.swift +++ b/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/Views/AttendanceScoreComponents/MyInformationWithScoreView.swift @@ -28,13 +28,6 @@ final class MyInformationWithScoreView: UIView { private let currentScoreLabel = UILabel() - private lazy var infoButton: UIButton = { - let button = UIButton(type: .system) - button.setImage(DSKitAsset.Assets.opInfo.image, for: .normal) - button.addTarget(self, action: #selector(infoButtonDidTap), for: .touchUpInside) - return button - }() - // MARK: - Initialization override init(frame: CGRect) { @@ -53,7 +46,7 @@ extension MyInformationWithScoreView { private func setLayout() { - addSubviews(nameLabel, currentScoreLabel, infoButton) + addSubviews(nameLabel, currentScoreLabel) nameLabel.snp.makeConstraints { $0.top.leading.equalToSuperview() @@ -63,25 +56,13 @@ extension MyInformationWithScoreView { $0.top.equalTo(nameLabel.snp.bottom).offset(8) $0.leading.equalToSuperview() } - - infoButton.snp.makeConstraints { - $0.trailing.bottom.equalToSuperview() - } } } - // MARK: - Methods extension MyInformationWithScoreView { - @objc - private func infoButtonDidTap() { - if let url = URL(string: "https://sopt.org/rules") { - UIApplication.shared.open(url) - } - } - func setData(name: String, part: String, generation: Int, count: Double) { nameLabel.text = "\(generation)기 \(part)파트 \(name)" chageFontAndColor(with: "\(doubleToString(count))점") diff --git a/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/Views/AttendanceScoreComponents/SingleScoreView.swift b/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/Views/AttendanceScoreComponents/SingleScoreView.swift index 3610eaa7..a47f3d86 100644 --- a/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/Views/AttendanceScoreComponents/SingleScoreView.swift +++ b/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/Views/AttendanceScoreComponents/SingleScoreView.swift @@ -37,9 +37,7 @@ final class SingleScoreView: UIView { init(type: AttendanceStateType, count: Int = 0) { super.init(frame: .zero) - updateScoreTypeLabel(type) setLayout(type) - setData(count) } required init?(coder: NSCoder) { @@ -51,10 +49,6 @@ final class SingleScoreView: UIView { extension SingleScoreView { - private func updateScoreTypeLabel(_ type: AttendanceStateType) { - singleScoreTitleLabel.text = type.rawValue - } - private func setLayout(_ type: AttendanceStateType) { addSubviews(singleScoreTitleLabel, singleScoreCountLabel) @@ -69,13 +63,22 @@ extension SingleScoreView { $0.centerX.equalToSuperview() } } + + private func setDefaultLayout(_ type: AttendanceStateType) { + updateScoreTypeLabel(type) + } + + private func updateScoreTypeLabel(_ type: AttendanceStateType) { + singleScoreTitleLabel.text = type.korean + } } // MARK: - Methods extension SingleScoreView { - func setData(_ count: Int) { + func setData(_ count: Int, _ type: AttendanceStateType) { + setDefaultLayout(type) singleScoreCountLabel.text = "\(count)회" } } diff --git a/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/Views/AttendanceScoreView.swift b/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/Views/AttendanceScoreView.swift index c57ff8ef..943e197d 100644 --- a/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/Views/AttendanceScoreView.swift +++ b/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/Views/AttendanceScoreView.swift @@ -30,13 +30,13 @@ final class AttendanceScoreView: UIView { /// 2. 전체 출결 점수 영역 - private let allScoreView = SingleScoreView(type: .all) private let attendanceScoreView = SingleScoreView(type: .attendance) private let tardyScoreView = SingleScoreView(type: .tardy) private let absentScoreView = SingleScoreView(type: .absent) + private let participateScoreView = SingleScoreView(type: .participate) private lazy var myScoreContainerStackView: UIStackView = { - let stackView = UIStackView(arrangedSubviews: [allScoreView, attendanceScoreView, tardyScoreView, absentScoreView]) + let stackView = UIStackView(arrangedSubviews: [attendanceScoreView, tardyScoreView, absentScoreView, participateScoreView]) stackView.backgroundColor = DSKitAsset.Colors.black40.color stackView.clipsToBounds = true stackView.layer.cornerRadius = 8 @@ -53,7 +53,6 @@ final class AttendanceScoreView: UIView { private let attendanceScoreDescriptiopnLabel: UILabel = { let label = UILabel() label.font = .Main.body2 - label.text = I18N.Attendance.myAttendance label.textColor = DSKitAsset.Colors.gray60.color return label }() @@ -135,8 +134,7 @@ extension AttendanceScoreView { attendanceTableView.register(MyAttendanceStateTVC.self, forCellReuseIdentifier: MyAttendanceStateTVC.className) } - func updateTableviewHeight() { - + private func updateTableviewHeight() { attendanceTableView.snp.updateConstraints { $0.height.equalTo(attendanceModelList.count * 40) } @@ -146,19 +144,20 @@ extension AttendanceScoreView { myInfoContainerView.setData(name: name, part: part, generation: generation, count: count) } - func setMyTotalScoreData(attendance: Int, tardy: Int, absent: Int) { - allScoreView.setData(attendance + tardy + absent) - attendanceScoreView.setData(attendance) - tardyScoreView.setData(tardy) - absentScoreView.setData(absent) + func setMyTotalScoreData(attendance: Int, tardy: Int, absent: Int, participate: Int) { + attendanceScoreView.setData(attendance, .attendance) + tardyScoreView.setData(tardy, .tardy) + absentScoreView.setData(absent, .absent) + participateScoreView.setData(participate, .participate) } func setMyAttendanceTableData(_ model: [AttendanceModel]) { + attendanceScoreDescriptiopnLabel.text = I18N.Attendance.myAttendance + attendanceModelList = model updateTableviewHeight() attendanceTableView.reloadData() } - } // MARK: - UITableViewDelegate, UITableViewDataSource diff --git a/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/Views/TodayScheduleView.swift b/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/Views/TodayScheduleView.swift index 677423f4..899c415d 100644 --- a/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/Views/TodayScheduleView.swift +++ b/SOPT-iOS/Projects/Features/AttendanceFeature/Sources/ShowAttendanceScene/Views/TodayScheduleView.swift @@ -22,7 +22,6 @@ final class TodayScheduleView: UIView { private let dateImageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleToFill - imageView.image = DSKitAsset.Assets.opDate.image return imageView }() @@ -36,7 +35,6 @@ final class TodayScheduleView: UIView { private let placeImageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleToFill - imageView.image = DSKitAsset.Assets.opPlace.image return imageView }() @@ -50,7 +48,6 @@ final class TodayScheduleView: UIView { private let titleLabel: UILabel = { let label = UILabel() label.textColor = .white - label.text = I18N.Attendance.today + I18N.Attendance.unscheduledDay + I18N.Attendance.dayIs label.font = .Main.headline2 return label }() @@ -148,6 +145,7 @@ extension TodayScheduleView { if case .unscheduledDay = type { todayInfoStackView.isHidden = true + titleLabel.text = I18N.Attendance.today + I18N.Attendance.unscheduledDay + I18N.Attendance.dayIs addSubview(titleLabel) titleLabel.snp.makeConstraints { $0.top.bottom.equalToSuperview().inset(32) @@ -155,11 +153,19 @@ extension TodayScheduleView { } } } + + private func setDefaultLayout() { + dateImageView.image = DSKitAsset.Assets.opDate.image + placeImageView.image = DSKitAsset.Assets.opPlace.image + } } +// MARK: - Methods + extension TodayScheduleView { func setData(date: String, place: String, todaySchedule: String, description: String?) { + setDefaultLayout() dateLabel.text = date placeLabel.text = place titleLabel.text = I18N.Attendance.today + todaySchedule + I18N.Attendance.dayIs diff --git a/SOPT-iOS/Projects/Features/AuthFeature/Sources/SignInScene/VC/SignInVC.swift b/SOPT-iOS/Projects/Features/AuthFeature/Sources/SignInScene/VC/SignInVC.swift index d2438cb4..020d1a9e 100644 --- a/SOPT-iOS/Projects/Features/AuthFeature/Sources/SignInScene/VC/SignInVC.swift +++ b/SOPT-iOS/Projects/Features/AuthFeature/Sources/SignInScene/VC/SignInVC.swift @@ -196,16 +196,21 @@ extension SignInVC { output.isSignInSuccess .removeDuplicates() - .sink { [weak self] isSuccessed in - guard let self = self else { return } - if isSuccessed { - self.setRootViewToMain() - } - }.store(in: self.cancelBag) + .sink { [weak self] type in + guard let self = self else { return } + + switch type { + case .loginSuccess: + self.setRootViewToMain() + case .unregistedProfile: + self.setRootViewToMain(isInActiveUser: true) + case .loginFailure: break + } + }.store(in: self.cancelBag) } - private func setRootViewToMain() { - let userType = UserDefaultKeyList.Auth.getUserType() + private func setRootViewToMain(isInActiveUser: Bool = false) { + let userType = isInActiveUser ? .unregisteredInactive : UserDefaultKeyList.Auth.getUserType() let navigation = UINavigationController(rootViewController: factory.makeMainVC(userType: userType).viewController) ViewControllerUtils.setRootViewController(window: self.view.window!, viewController: navigation, withAnimation: true) } diff --git a/SOPT-iOS/Projects/Features/AuthFeature/Sources/SignInScene/ViewModel/SignInViewModel.swift b/SOPT-iOS/Projects/Features/AuthFeature/Sources/SignInScene/ViewModel/SignInViewModel.swift index 8521e0ad..49e89d96 100644 --- a/SOPT-iOS/Projects/Features/AuthFeature/Sources/SignInScene/ViewModel/SignInViewModel.swift +++ b/SOPT-iOS/Projects/Features/AuthFeature/Sources/SignInScene/ViewModel/SignInViewModel.swift @@ -25,7 +25,7 @@ public class SignInViewModel: ViewModelType { // MARK: - Outputs public struct Output { - var isSignInSuccess = PassthroughSubject() + var isSignInSuccess = PassthroughSubject() } // MARK: - init diff --git a/SOPT-iOS/Projects/Features/BaseFeatureDependency/Sources/Alert/AlertVC.swift b/SOPT-iOS/Projects/Features/BaseFeatureDependency/Sources/Alert/AlertVC.swift index 4721e132..f966a2a4 100644 --- a/SOPT-iOS/Projects/Features/BaseFeatureDependency/Sources/Alert/AlertVC.swift +++ b/SOPT-iOS/Projects/Features/BaseFeatureDependency/Sources/Alert/AlertVC.swift @@ -153,7 +153,7 @@ extension AlertVC { descriptionLabel.snp.makeConstraints { make in make.centerX.equalToSuperview() make.centerY.equalToSuperview().offset(-6) - make.leading.trailing.equalToSuperview().inset(30) + make.leading.trailing.equalToSuperview().inset(7) } } } diff --git a/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/Cells/UsersHistory/UserHistoryCVC.swift b/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/Cells/UsersHistory/UserHistoryCVC.swift index 2699d88f..c0df368e 100644 --- a/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/Cells/UsersHistory/UserHistoryCVC.swift +++ b/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/Cells/UsersHistory/UserHistoryCVC.swift @@ -95,7 +95,8 @@ extension UserHistoryCVC { } // 플그에 기수 정보 입력 안한 비활동 회원 대응 (추후 제거) - if userType == .inactive { + if userType == .inactive || userType == .unregisteredInactive { + self.userTypeLabel.backgroundColor = DSKitAsset.Colors.black40.color if allHistory == nil { self.userTypeLabel.text = I18N.Main.inactiveMember } diff --git a/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/Cells/UsersHistory/UserHistoryHeaderView.swift b/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/Cells/UsersHistory/UserHistoryHeaderView.swift index 3d7e61ed..a60eeb8e 100644 --- a/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/Cells/UsersHistory/UserHistoryHeaderView.swift +++ b/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/Cells/UsersHistory/UserHistoryHeaderView.swift @@ -62,7 +62,7 @@ extension UserHistoryHeaderView { if userType == .visitor { let text = I18N.Main.encourage setAttributedTextToUserInfoLabel(text: text, name: nil) - } else if userType == .inactive { + } else if userType == .inactive || userType == .unregisteredInactive { let text = I18N.Main.welcome setAttributedTextToUserInfoLabel(text: text, name: nil) } diff --git a/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/VC/MainVC.swift b/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/VC/MainVC.swift index 311045d4..5e8e735e 100644 --- a/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/VC/MainVC.swift +++ b/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/VC/MainVC.swift @@ -103,17 +103,30 @@ extension MainVC { output.getUserMainInfoDidComplete .sink { [weak self] _ in - guard let userMainInfo = self?.viewModel.userMainInfo, userMainInfo.withError == false else { + guard let userMainInfo = self?.viewModel.userMainInfo else { + self?.collectionView.reloadData() + return + } + + guard userMainInfo.withError == false else { self?.presentNetworkAlertVC() return } self?.collectionView.reloadData() }.store(in: self.cancelBag) - + output.isServiceAvailable .sink { isServiceAvailable in print("현재 앱 서비스 사용 가능(심사 X)?: \(isServiceAvailable)") }.store(in: self.cancelBag) + + // 플그 프로필 미등록 유저 알림 + output.needPlaygroundProfileRegistration + .sink { [weak self] needRegistration in + if needRegistration { + self?.presentPlaygroundRegisterationAlertVC() + } + }.store(in: self.cancelBag) } private func bindViews() { @@ -174,6 +187,19 @@ extension MainVC { self.present(networkAlertVC, animated: false) } + + private func presentPlaygroundRegisterationAlertVC() { + let alertVC = self.factory.makeAlertVC( + type: .networkErr, + theme: .main, + title: I18N.Main.failedToGetUserInfo, + description: I18N.Main.needToRegisterPlayground, + customButtonTitle: "", + customAction: nil) + .viewController + + self.present(alertVC, animated: false) + } } // MARK: - UICollectionViewDelegate @@ -190,24 +216,29 @@ extension MainVC: UICollectionViewDelegate { self.navigationController?.pushViewController(viewController, animated: true) return } - - let safariViewController = SFSafariViewController(url: URL(string: service.serviceDomainLink)!) - safariViewController.playgroundStyle() - self.present(safariViewController, animated: true) - + + var needOfficialProject = service == .project && viewModel.userType == .visitor + let serviceDomainURL = needOfficialProject + ? ExternalURL.SOPT.project + : service.serviceDomainLink + showSafariVC(url: serviceDomainURL) case (2, _): guard let service = viewModel.otherServiceList[safe: indexPath.item] else { return } - let safariViewController = SFSafariViewController(url: URL(string: service.serviceDomainLink)!) - safariViewController.playgroundStyle() - self.present(safariViewController, animated: true) + showSafariVC(url: service.serviceDomainLink) case(3, _): - guard viewModel.userType != .visitor else { return } + guard viewModel.userType != .visitor && viewModel.userType != .unregisteredInactive else { return } presentSoptampFeature() default: break } } + + private func showSafariVC(url: String) { + let safariViewController = SFSafariViewController(url: URL(string: url)!) + safariViewController.playgroundStyle() + self.present(safariViewController, animated: true) + } } // MARK: - UICollectionViewDataSource diff --git a/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/ViewModel/MainViewModel.swift b/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/ViewModel/MainViewModel.swift index 53513711..f51d60a3 100644 --- a/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/ViewModel/MainViewModel.swift +++ b/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/ViewModel/MainViewModel.swift @@ -36,6 +36,7 @@ public class MainViewModel: ViewModelType { public struct Output { var getUserMainInfoDidComplete = PassthroughSubject() var isServiceAvailable = PassthroughSubject() + var needPlaygroundProfileRegistration = PassthroughSubject() } // MARK: - init @@ -69,8 +70,14 @@ extension MainViewModel { private func bindOutput(output: Output, cancelBag: CancelBag) { useCase.userMainInfo.asDriver() .sink { [weak self] userMainInfo in - self?.userMainInfo = userMainInfo + guard let self = self else { return } + self.userMainInfo = userMainInfo + self.userType = userMainInfo?.userType ?? .unregisteredInactive + self.setServiceList(with: self.userType) output.getUserMainInfoDidComplete.send() + if self.userType == .unregisteredInactive { + output.needPlaygroundProfileRegistration.send(true) + } }.store(in: self.cancelBag) useCase.serviceState.asDriver() @@ -88,7 +95,7 @@ extension MainViewModel { case .active: self.mainServiceList = [.attendance, .member, .project] self.otherServiceList = [.officialHomepage, .crew] - case .inactive: + case .inactive, .unregisteredInactive: self.mainServiceList = [.faq, .member, .project] self.otherServiceList = [.crew, .officialHomepage] } diff --git a/SOPT-iOS/Projects/Features/SettingFeature/Interface/Sources/SettingFeatureViewControllable.swift b/SOPT-iOS/Projects/Features/SettingFeature/Interface/Sources/SettingFeatureViewControllable.swift index b01da894..432c6983 100644 --- a/SOPT-iOS/Projects/Features/SettingFeature/Interface/Sources/SettingFeatureViewControllable.swift +++ b/SOPT-iOS/Projects/Features/SettingFeature/Interface/Sources/SettingFeatureViewControllable.swift @@ -6,6 +6,8 @@ // Copyright © 2023 SOPT-iOS. All rights reserved. // +import Core + import BaseFeatureDependency public protocol SettingViewControllable: ViewControllable { } @@ -18,7 +20,9 @@ public protocol PrivacyPolicyViewControllable: ViewControllable { } public protocol TermsOfServiceViewControllable: ViewControllable { } -public protocol WithdrawalViewControllable: ViewControllable { } +public protocol WithdrawalViewControllable: ViewControllable { + var userType: UserType { get set } +} public protocol SettingFeatureViewBuildable { func makeSettingVC() -> SettingViewControllable @@ -26,5 +30,5 @@ public protocol SettingFeatureViewBuildable { func makeSentenceEditVC() -> SentenceEditViewControllable func makePrivacyPolicyVC() -> PrivacyPolicyViewControllable func makeTermsOfServiceVC() -> TermsOfServiceViewControllable - func makeWithdrawalVC() -> WithdrawalViewControllable + func makeWithdrawalVC(userType: UserType) -> WithdrawalViewControllable } diff --git a/SOPT-iOS/Projects/Features/SettingFeature/Sources/SettingScene/VC/SettingVC.swift b/SOPT-iOS/Projects/Features/SettingFeature/Sources/SettingScene/VC/SettingVC.swift index be7f6ad0..93e8bc22 100644 --- a/SOPT-iOS/Projects/Features/SettingFeature/Sources/SettingScene/VC/SettingVC.swift +++ b/SOPT-iOS/Projects/Features/SettingFeature/Sources/SettingScene/VC/SettingVC.swift @@ -156,7 +156,7 @@ extension SettingVC { extension SettingVC: WithdrawButtonDelegate { func withdrawButtonTapped() { - let withdrawalVC = factory.makeWithdrawalVC().viewController + let withdrawalVC = factory.makeWithdrawalVC(userType: .visitor).viewController navigationController?.pushViewController(withdrawalVC, animated: true) } } diff --git a/SOPT-iOS/Projects/Features/SettingFeature/Sources/SettingScene/VC/WithdrawalVC.swift b/SOPT-iOS/Projects/Features/SettingFeature/Sources/SettingScene/VC/WithdrawalVC.swift index b6939228..b28b74e7 100644 --- a/SOPT-iOS/Projects/Features/SettingFeature/Sources/SettingScene/VC/WithdrawalVC.swift +++ b/SOPT-iOS/Projects/Features/SettingFeature/Sources/SettingScene/VC/WithdrawalVC.swift @@ -9,6 +9,7 @@ import UIKit import DSKit import SafariServices +import Combine import Core @@ -23,6 +24,7 @@ public class WithdrawalVC: UIViewController, WithdrawalViewControllable { public var viewModel: WithdrawalViewModel! public var factory: (AuthFeatureViewBuildable & AlertViewBuildable)! private let cancelBag = CancelBag() + public var userType: UserType = .active // MARK: - UI Components @@ -119,6 +121,18 @@ extension WithdrawalVC { let withdrawalButtonTapped = self.withdrawalButton .publisher(for: .touchUpInside) + .withUnretained(self) + .filter({ owner, _ in + guard owner.userType != .unregisteredInactive else { + owner.showLoading() + DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) { + owner.showToastAndChangeRootView() + owner.stopLoading() + } + return false + } + return true + }) .mapVoid() .asDriver() diff --git a/SOPT-iOS/Projects/Features/StampFeature/Sources/MissionListScene/Cells/MissionListCVC.swift b/SOPT-iOS/Projects/Features/StampFeature/Sources/MissionListScene/Cells/MissionListCVC.swift index 189a9207..ea5b6a34 100644 --- a/SOPT-iOS/Projects/Features/StampFeature/Sources/MissionListScene/Cells/MissionListCVC.swift +++ b/SOPT-iOS/Projects/Features/StampFeature/Sources/MissionListScene/Cells/MissionListCVC.swift @@ -155,6 +155,7 @@ final class MissionListCVC: UICollectionViewCell, UICollectionViewRegisterable { private let purposeLabel: UILabel = { let label = UILabel() label.text = "세미나 끝나고 뒷풀이 2시까지 달리기" + label.textColor = .black label.setTypoStyle(.SoptampFont.caption1D) label.numberOfLines = 2 return label diff --git a/SOPT-iOS/Projects/Modules/DSKit/Sources/Components/OperationComponents/OPNavigationBar.swift b/SOPT-iOS/Projects/Modules/DSKit/Sources/Components/OperationComponents/OPNavigationBar.swift index b9446f60..69ddb6a7 100644 --- a/SOPT-iOS/Projects/Modules/DSKit/Sources/Components/OperationComponents/OPNavigationBar.swift +++ b/SOPT-iOS/Projects/Modules/DSKit/Sources/Components/OperationComponents/OPNavigationBar.swift @@ -39,7 +39,7 @@ public final class OPNavigationBar: UIView { // MARK: - initialization - public init(_ vc: UIViewController, type: OPNaviType, backgroundColor: UIColor = .black) { + public init(_ vc: UIViewController, type: OPNaviType, backgroundColor: UIColor = DSKitAsset.Colors.black100.color) { super.init(frame: .zero) self.vc = vc self.setUI(type, backgroundColor: backgroundColor) diff --git a/SOPT-iOS/Projects/Modules/Network/Sources/Foundation/BaseService.swift b/SOPT-iOS/Projects/Modules/Network/Sources/Foundation/BaseService.swift index 0af8f1e7..305f98b1 100644 --- a/SOPT-iOS/Projects/Modules/Network/Sources/Foundation/BaseService.swift +++ b/SOPT-iOS/Projects/Modules/Network/Sources/Foundation/BaseService.swift @@ -14,6 +14,19 @@ import Core import Alamofire import Moya + +public enum SOPTAPPError: Error { + case network(statusCode: Int) + case unknown + + init(error: Error, statusCode: Int? = 0) { + guard let statusCode else { self = .unknown ; return } + + self = .network(statusCode: statusCode) + } +} + + open class BaseService { typealias API = Target @@ -88,6 +101,35 @@ extension BaseService { } }.eraseToAnyPublisher() } + + func requestObjectWithNetworkErrorInCombine(_ target: API) -> AnyPublisher { + return Future { promise in + self.provider.request(target) { response in + switch response { + case .success(let value): + do { + guard let response = value.response else { throw NSError(domain: "이 경우는 생각 업씀", code: -1000) } + + switch response.statusCode { + case 200...399: + let decoder = JSONDecoder() + let body = try decoder.decode(T.self, from: value.data) + promise(.success(body)) + case 400...599: + // NOTE: (@승호) 여기에서 서버와 에러 처리 핸들링 해서 Error도 json Decode 해야 함 + // 임시로 Error 처리 + throw SOPTAPPError(error: NSError(domain: "임시에러", code: -1001), statusCode: response.statusCode) + default: break + } + } catch let error { + promise(.failure(error)) + } + case .failure(let error): + promise(.failure(error)) + } + } + }.eraseToAnyPublisher() + } func requestObjectInCombineNoResult(_ target: API) -> AnyPublisher { return Future { promise in diff --git a/SOPT-iOS/Projects/Modules/Network/Sources/Service/AuthService.swift b/SOPT-iOS/Projects/Modules/Network/Sources/Service/AuthService.swift index facca679..88f0ca66 100644 --- a/SOPT-iOS/Projects/Modules/Network/Sources/Service/AuthService.swift +++ b/SOPT-iOS/Projects/Modules/Network/Sources/Service/AuthService.swift @@ -20,6 +20,6 @@ public protocol AuthService { extension DefaultAuthService: AuthService { public func signIn(token: String) -> AnyPublisher { - return requestObjectInCombine(.signIn(token: token)) + return requestObjectWithNetworkErrorInCombine(.signIn(token: token)) } } diff --git a/SOPT-iOS/Projects/SOPT-iOS/Sources/Application/SceneDelegate.swift b/SOPT-iOS/Projects/SOPT-iOS/Sources/Application/SceneDelegate.swift index cf314e71..8d562e5c 100644 --- a/SOPT-iOS/Projects/SOPT-iOS/Sources/Application/SceneDelegate.swift +++ b/SOPT-iOS/Projects/SOPT-iOS/Sources/Application/SceneDelegate.swift @@ -25,6 +25,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window?.windowScene = scene // let rootVC = container.makeShowAttendanceVC().viewController let rootVC = container.makeSplashVC().viewController +// let rootVC = container.makeMainVC(userType: .active).viewController + window?.rootViewController = UINavigationController(rootViewController: rootVC) window?.makeKeyAndVisible() } diff --git a/SOPT-iOS/Projects/SOPT-iOS/Sources/ModuleFactory/DIContainer.swift b/SOPT-iOS/Projects/SOPT-iOS/Sources/ModuleFactory/DIContainer.swift index ea047aed..c5148c95 100644 --- a/SOPT-iOS/Projects/SOPT-iOS/Sources/ModuleFactory/DIContainer.swift +++ b/SOPT-iOS/Projects/SOPT-iOS/Sources/ModuleFactory/DIContainer.swift @@ -236,13 +236,14 @@ extension DIContainer: Features { return termsOfServiceVC } - func makeWithdrawalVC() -> WithdrawalViewControllable { + func makeWithdrawalVC(userType: UserType) -> WithdrawalViewControllable { let withdrawalVC = WithdrawalVC() let repository = SettingRepository(authService: authService, stampService: stampService, userService: userService) let useCase = DefaultSettingUseCase(repository: repository) let viewModel = WithdrawalViewModel(useCase: useCase) withdrawalVC.viewModel = viewModel withdrawalVC.factory = self + withdrawalVC.userType = userType return withdrawalVC } diff --git a/SOPT-iOS/Tuist/Dependencies.swift b/SOPT-iOS/Tuist/Dependencies.swift index 49042f1d..7c42f5f1 100644 --- a/SOPT-iOS/Tuist/Dependencies.swift +++ b/SOPT-iOS/Tuist/Dependencies.swift @@ -14,12 +14,12 @@ let spm = SwiftPackageManagerDependencies([ .remote(url: "https://github.com/SnapKit/SnapKit.git", requirement: .upToNextMinor(from: "5.0.0")), .remote(url: "https://github.com/Moya/Moya.git", requirement: .upToNextMajor(from: "15.0.0")), .remote(url: "https://github.com/devxoul/Then", requirement: .upToNextMajor(from: "2")), - .remote(url: "https://github.com/onevcat/Kingfisher.git", requirement: .upToNextMajor(from: "7.0.0")), + .remote(url: "https://github.com/onevcat/Kingfisher.git", requirement: .upToNextMajor(from: "7.6.2")), .remote(url: "https://github.com/FLEXTool/FLEX.git", requirement: .upToNextMajor(from: "4.3.0")), .remote(url: "https://github.com/krzysztofzablocki/Inject.git", requirement: .upToNextMajor(from: "1.0.5")), .remote(url: "https://github.com/Quick/Quick.git", requirement: .upToNextMajor(from: "5.0.0")), .remote(url: "https://github.com/Quick/Nimble.git", requirement: .upToNextMajor(from: "10.0.0")), - .remote(url: "https://github.com/airbnb/lottie-ios", requirement: .upToNextMajor(from: "3.0.0")) + .remote(url: "https://github.com/airbnb/lottie-ios", requirement: .upToNextMajor(from: "4.1.3")) ], baseSettings: Settings.settings( configurations: XCConfig.framework ))