diff --git a/Smeem-iOS/Smeem-iOS.xcodeproj/project.pbxproj b/Smeem-iOS/Smeem-iOS.xcodeproj/project.pbxproj index 2f3ed733..4632f551 100644 --- a/Smeem-iOS/Smeem-iOS.xcodeproj/project.pbxproj +++ b/Smeem-iOS/Smeem-iOS.xcodeproj/project.pbxproj @@ -59,6 +59,7 @@ 37ADCC022B0289AD00E474AA /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37ADCC012B0289AD00E474AA /* Observable.swift */; }; 37B360AA2AC2A4680006C8ED /* SmeemTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B360A92AC2A4680006C8ED /* SmeemTextView.swift */; }; 37BCADF22BC3FFBF006EF960 /* DetailDiaryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BCADF12BC3FFBF006EF960 /* DetailDiaryViewModel.swift */; }; + 37BDC9872C4FEA940075F68A /* SendFeedbackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BDC9862C4FEA940075F68A /* SendFeedbackView.swift */; }; 37DCA6572A47574300FF8F90 /* RandomTopicAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DCA6562A47574300FF8F90 /* RandomTopicAPI.swift */; }; 37DCA65A2A47598700FF8F90 /* RandomTopicService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DCA6592A47598700FF8F90 /* RandomTopicService.swift */; }; 37DCA65D2A475B5100FF8F90 /* RandomTopicResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DCA65C2A475B5100FF8F90 /* RandomTopicResponse.swift */; }; @@ -297,6 +298,7 @@ 37ADCC012B0289AD00E474AA /* Observable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Observable.swift; sourceTree = ""; }; 37B360A92AC2A4680006C8ED /* SmeemTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmeemTextView.swift; sourceTree = ""; }; 37BCADF12BC3FFBF006EF960 /* DetailDiaryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailDiaryViewModel.swift; sourceTree = ""; }; + 37BDC9862C4FEA940075F68A /* SendFeedbackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendFeedbackView.swift; sourceTree = ""; }; 37DCA6562A47574300FF8F90 /* RandomTopicAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RandomTopicAPI.swift; sourceTree = ""; }; 37DCA6592A47598700FF8F90 /* RandomTopicService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RandomTopicService.swift; sourceTree = ""; }; 37DCA65C2A475B5100FF8F90 /* RandomTopicResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RandomTopicResponse.swift; sourceTree = ""; }; @@ -1542,6 +1544,7 @@ 4AC705902BEA1E49003C5310 /* PlanContainerView.swift */, 4AC705922BEA1E52003C5310 /* LanguageContainerView.swift */, 4AC705942BEA1E58003C5310 /* AlarmContainerView.swift */, + 37BDC9862C4FEA940075F68A /* SendFeedbackView.swift */, ); path = View; sourceTree = ""; @@ -1939,6 +1942,7 @@ 4AF58CE92A4820AA00305248 /* AuthNetworkModel.swift in Sources */, 378E461F2B551A6F00D2A473 /* DeepLResponse.swift in Sources */, 3761116C2A278D0E0095EC5A /* String+.swift in Sources */, + 37BDC9872C4FEA940075F68A /* SendFeedbackView.swift in Sources */, 4A07F8AC2B7B3E7F004185F2 /* SignupViewModel.swift in Sources */, 4AB7C91B2B7624CC00845733 /* ServiceAcceptAppData.swift in Sources */, 4AC707B42B73D2D400B1D791 /* UserNicknameViewModel.swift in Sources */, diff --git a/Smeem-iOS/Smeem-iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Smeem-iOS/Smeem-iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 74b92f93..0405f1e3 100644 --- a/Smeem-iOS/Smeem-iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Smeem-iOS/Smeem-iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/abseil-cpp-binary.git", "state" : { - "revision" : "bfc0b6f81adc06ce5121eb23f628473638d67c5c", - "version" : "1.2022062300.0" + "revision" : "df308b8b46607675f2b9ec8e569109008f9155ce", + "version" : "1.2022062300.1" } }, { @@ -68,8 +68,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleDataTransport.git", "state" : { - "revision" : "7874c1b48cbffd086ce8a052c4be873a78613775", - "version" : "9.2.3" + "revision" : "a637d318ae7ae246b02d7305121275bc75ed5565", + "version" : "9.4.0" } }, { @@ -77,8 +77,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleUtilities.git", "state" : { - "revision" : "871d43135925cde39ef7421d8723ce47edfdcc39", - "version" : "7.11.1" + "revision" : "57a1d307f42df690fdef2637f3e5b776da02aad6", + "version" : "7.13.3" } }, { @@ -86,8 +86,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/grpc-binary.git", "state" : { - "revision" : "2b7656d8ee892cd39db460b1a0d849de618405e6", - "version" : "1.50.1" + "revision" : "f1b366129d1125be7db83247e003fc333104b569", + "version" : "1.50.2" } }, { @@ -95,8 +95,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/gtm-session-fetcher.git", "state" : { - "revision" : "d415594121c9e8a4f9d79cecee0965cf35e74dbd", - "version" : "3.1.1" + "revision" : "a2ab612cb980066ee56d90d60d8462992c07f24b", + "version" : "3.5.0" } }, { @@ -122,8 +122,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/leveldb.git", "state" : { - "revision" : "0706abcc6b0bd9cedfbb015ba840e4a780b5159b", - "version" : "1.22.2" + "revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1", + "version" : "1.22.5" } }, { @@ -149,8 +149,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/promises.git", "state" : { - "revision" : "ec957ccddbcc710ccc64c9dcbd4c7006fcf8b73a", - "version" : "2.2.0" + "revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac", + "version" : "2.4.0" } }, { @@ -167,8 +167,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/ReactiveX/RxSwift.git", "state" : { - "revision" : "9dcaa4b333db437b0fbfaf453fad29069044a8b4", - "version" : "6.6.0" + "revision" : "b06a8c8596e4c3e8e7788e08e720e3248563ce6a", + "version" : "6.7.1" } }, { @@ -185,8 +185,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-protobuf.git", "state" : { - "revision" : "0af9125c4eae12a4973fb66574c53a54962a9e1e", - "version" : "1.21.0" + "revision" : "d57a5aecf24a25b32ec4a74be2f5d0a995a47c4b", + "version" : "1.27.0" } } ], diff --git a/Smeem-iOS/Smeem-iOS/Global/UIComponents/Alarm/AlarmCollectionView.swift b/Smeem-iOS/Smeem-iOS/Global/UIComponents/Alarm/AlarmCollectionView.swift index 5cc4a1b6..23f4522c 100644 --- a/Smeem-iOS/Smeem-iOS/Global/UIComponents/Alarm/AlarmCollectionView.swift +++ b/Smeem-iOS/Smeem-iOS/Global/UIComponents/Alarm/AlarmCollectionView.swift @@ -70,6 +70,7 @@ final class AlarmCollectionView: UICollectionView { setCellRegister() setViewRegister() setLayerUI() + self.isScrollEnabled = false } required init?(coder: NSCoder) { diff --git a/Smeem-iOS/Smeem-iOS/Global/UIComponents/SmeemComponent/SmeemToastView.swift b/Smeem-iOS/Smeem-iOS/Global/UIComponents/SmeemComponent/SmeemToastView.swift index 4b3a68e2..4e497d58 100644 --- a/Smeem-iOS/Smeem-iOS/Global/UIComponents/SmeemComponent/SmeemToastView.swift +++ b/Smeem-iOS/Smeem-iOS/Global/UIComponents/SmeemComponent/SmeemToastView.swift @@ -81,6 +81,15 @@ final class SmeemToastView: UIView { return imageView }() + private let labelStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.distribution = .fill + stackView.spacing = 3 + stackView.alignment = .leading + return stackView + }() + private let headLabel: UILabel = { let label = UILabel() label.font = .c1 @@ -169,7 +178,8 @@ final class SmeemToastView: UIView { } private func setToastViewLayout() { - addSubviews(cautionImage, headLabel, bodyLabel) + addSubviews(cautionImage, labelStackView) + labelStackView.addArrangedSubviews(headLabel, bodyLabel) switch type { case .smeemToast: @@ -184,14 +194,9 @@ final class SmeemToastView: UIView { make.width.height.equalTo(22) } - headLabel.snp.makeConstraints { make in - make.top.equalToSuperview().offset(14) + labelStackView.snp.makeConstraints { make in make.leading.equalTo(cautionImage.snp.trailing).offset(14) - } - - bodyLabel.snp.makeConstraints { make in - make.top.equalTo(headLabel.snp.bottom).offset(3) - make.leading.equalTo(headLabel) + make.centerY.equalTo(cautionImage) } } @@ -202,7 +207,7 @@ final class SmeemToastView: UIView { if case .smeemToast = type { make.height.equalTo(convertByHeightRatio(50)) } else { - make.height.equalTo(convertByHeightRatio(70)) + make.height.equalTo(convertByWidthRatio(70)) } } } diff --git a/Smeem-iOS/Smeem-iOS/Presentation/Setting/VC/SettingViewController.swift b/Smeem-iOS/Smeem-iOS/Presentation/Setting/VC/SettingViewController.swift index b6bb7afe..d2d1fde8 100644 --- a/Smeem-iOS/Smeem-iOS/Presentation/Setting/VC/SettingViewController.swift +++ b/Smeem-iOS/Smeem-iOS/Presentation/Setting/VC/SettingViewController.swift @@ -8,6 +8,8 @@ import UIKit import Combine +import SnapKit + final class SettingViewController: BaseViewController { private let viewWillAppearSubject = PassthroughSubject() @@ -15,17 +17,14 @@ final class SettingViewController: BaseViewController { private let planButtonTapped = PassthroughSubject() private let alarmToggleTapped = PassthroughSubject() private let alarmButtnTapped = PassthroughSubject() + private let errorSubject = PassthroughSubject() private var cancelBag = Set() private let toastSubject = PassthroughSubject() private let viewModel = SettingViewModel(provider: SettingService()) - private let summaryScrollerView: UIScrollView = { - let scrollerView = UIScrollView() - scrollerView.showsVerticalScrollIndicator = false - return scrollerView - }() - + private let summaryScrollerView = UIScrollView() private let contentView = UIView() + private let naviView = UIView() private let backButton: UIButton = { @@ -48,12 +47,14 @@ final class SettingViewController: BaseViewController { return button }() - private let planContainerView = PlanContainerView() private let nicknameContainerView = NicknameContainerView() + private let planContainerView = PlanContainerView() private let languageContainerView = LanguageContainerView() private let alarmContainerView = AlarmContainerView() private let alarmCollectionContainerView = UIView() private let alarmCollectionView = AlarmCollectionView() + private let separationLine = SeparationLine(height: .thin) + private let sendFeedbackView = SendFeedbackView() override func viewDidLoad() { super.viewDidLoad() @@ -106,11 +107,24 @@ final class SettingViewController: BaseViewController { } .store(in: &cancelBag) + sendFeedbackView.directButtonTapped + .sink { _ in + guard let url = URL(string: "https://walla.my/survey/2SAyT8aWPKjqaL4cZ5vm") else { return } + UIApplication.shared.open(url, options: [:]) { success in + if success { + self.errorSubject.send(.clientError) + } + } + + } + .store(in: &cancelBag) + let output = viewModel.transform(input: SettingViewModel.Input(viewWillAppearSubject: viewWillAppearSubject, alarmToggleSubject: alarmToggleTapped, nicknameButtonTapped: nicknameButtonTapped, planButtonTapped: planButtonTapped, - alarmButtonTapped: alarmButtnTapped)) + alarmButtonTapped: alarmButtnTapped, + errorSubject: errorSubject)) output.hasPlanResult .receive(on: DispatchQueue.main) .sink { [weak self] response in @@ -215,8 +229,13 @@ final class SettingViewController: BaseViewController { view.addSubviews(naviView, summaryScrollerView) naviView.addSubviews(backButton, summaryLabel, moreButton) summaryScrollerView.addSubview(contentView) - contentView.addSubviews(planContainerView, nicknameContainerView, - languageContainerView, alarmContainerView, alarmCollectionView) + contentView.addSubviews(nicknameContainerView, + planContainerView, + languageContainerView, + alarmContainerView, + alarmCollectionView, + separationLine, + sendFeedbackView) alarmCollectionView.addSubview(alarmCollectionContainerView) naviView.snp.makeConstraints { @@ -224,16 +243,6 @@ final class SettingViewController: BaseViewController { $0.height.equalTo(66) } - summaryScrollerView.snp.makeConstraints { - $0.top.equalTo(naviView.snp.bottom) - $0.leading.trailing.bottom.equalTo(view.safeAreaLayoutGuide) - } - - contentView.snp.makeConstraints { - $0.edges.equalTo(summaryScrollerView.contentLayoutGuide) - $0.width.equalTo(summaryScrollerView.frameLayoutGuide) - } - backButton.snp.makeConstraints { $0.centerY.equalToSuperview() $0.leading.equalToSuperview().inset(10) @@ -250,8 +259,20 @@ final class SettingViewController: BaseViewController { $0.height.width.equalTo(40) } + // MARK: - summaryScrollerView + + summaryScrollerView.snp.makeConstraints { + $0.top.equalTo(naviView.snp.bottom) + $0.leading.trailing.bottom.equalToSuperview() + } + + contentView.snp.makeConstraints { + $0.edges.equalToSuperview() + $0.leading.centerX.equalToSuperview() + } + nicknameContainerView.snp.makeConstraints { - $0.top.equalTo(naviView.snp.bottom).offset(18) + $0.top.equalToSuperview().offset(18) $0.leading.trailing.equalToSuperview().inset(18) $0.height.equalTo(convertByHeightRatio(87)) } @@ -277,13 +298,26 @@ final class SettingViewController: BaseViewController { alarmCollectionView.snp.makeConstraints { $0.top.equalTo(alarmContainerView.snp.bottom).offset(convertByHeightRatio(10)) $0.leading.trailing.equalToSuperview().inset(18) - $0.bottom.equalToSuperview().inset(convertByHeightRatio(80)) $0.height.equalTo(convertByHeightRatio(133)) } alarmCollectionContainerView.snp.makeConstraints { $0.edges.equalToSuperview() - $0.width.height.equalTo(alarmCollectionView) + } + + separationLine.snp.remakeConstraints { + $0.top.equalTo(alarmCollectionView.snp.bottom).offset(28) + $0.height.equalTo(1) + $0.leading.trailing.equalTo(nicknameContainerView) + } + + separationLine.backgroundColor = .gray100 + + sendFeedbackView.snp.makeConstraints { + $0.top.equalTo(separationLine.snp.bottom).offset(28) + $0.leading.trailing.equalTo(alarmContainerView) + $0.height.equalTo(convertByHeightRatio(88)) + $0.bottom.equalToSuperview() } } } diff --git a/Smeem-iOS/Smeem-iOS/Presentation/Setting/View/SendFeedbackView.swift b/Smeem-iOS/Smeem-iOS/Presentation/Setting/View/SendFeedbackView.swift new file mode 100644 index 00000000..4ef38113 --- /dev/null +++ b/Smeem-iOS/Smeem-iOS/Presentation/Setting/View/SendFeedbackView.swift @@ -0,0 +1,97 @@ +// +// SendFeedbackView.swift +// Smeem-iOS +// +// Created by Joon Baek on 2024/07/23. +// + +import UIKit +import Combine + +import SnapKit + +final class SendFeedbackView: UIView { + + private (set) var directButtonTapped = PassthroughSubject() + private var cancelBag = Set() + + private let titleLabel: UILabel = { + let label = UILabel() + label.font = .s1 + label.textColor = .smeemBlack + label.text = "의견 보내기" + return label + }() + + private let containerView: UIView = { + let view = UIView() + view.backgroundColor = .clear + view.layer.borderWidth = 1.5 + view.layer.borderColor = UIColor.gray100.cgColor + view.makeRoundCorner(cornerRadius: 6) + return view + }() + + private let detailLabel: UILabel = { + let label = UILabel() + label.font = .b4 + label.textColor = .smeemBlack + label.text = "스밈에 대한 의견을 남겨주세요 :)" + return label + }() + + private let directButton: UIButton = { + let button = UIButton() + button.titleLabel?.font = .c3 + button.setTitleColor(.point, for: .normal) + button.setTitle("바로가기", for: .normal) + return button + }() + + init() { + super.init(frame: .zero) + + setLayout() + bind() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func bind() { + directButton.tapPublisher + .sink { [weak self] _ in + self?.directButtonTapped.send(()) + } + .store(in: &cancelBag) + } + + private func setLayout() { + addSubviews(titleLabel, containerView) + containerView.addSubviews(detailLabel, directButton) + + titleLabel.snp.makeConstraints { + $0.top.leading.equalToSuperview() + } + + containerView.snp.makeConstraints { + $0.top.equalTo(titleLabel.snp.bottom).offset(12) + $0.leading.trailing.bottom.equalToSuperview() + $0.height.equalTo(convertByHeightRatio(54)) + } + + detailLabel.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.leading.equalToSuperview().offset(convertByWidthRatio(20)) + } + + directButton.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.trailing.equalToSuperview() + $0.width.equalTo(89) + $0.height.equalTo(54) + } + } +} + diff --git a/Smeem-iOS/Smeem-iOS/Presentation/Setting/ViewModel/SettingViewModel.swift b/Smeem-iOS/Smeem-iOS/Presentation/Setting/ViewModel/SettingViewModel.swift index 0b6f4048..793cbe1d 100644 --- a/Smeem-iOS/Smeem-iOS/Presentation/Setting/ViewModel/SettingViewModel.swift +++ b/Smeem-iOS/Smeem-iOS/Presentation/Setting/ViewModel/SettingViewModel.swift @@ -26,6 +26,7 @@ final class SettingViewModel: ViewModel { let nicknameButtonTapped: PassthroughSubject let planButtonTapped: PassthroughSubject let alarmButtonTapped: PassthroughSubject + let errorSubject: PassthroughSubject } struct Output { @@ -148,6 +149,8 @@ final class SettingViewModel: ViewModel { let loadingViewResult = loadingViewSubject.eraseToAnyPublisher() let errorResult = errorSubject.eraseToAnyPublisher() + let inputErrorResult = input.errorSubject.eraseToAnyPublisher() + let totalError = Publishers.Merge(errorResult, inputErrorResult).eraseToAnyPublisher() return Output(alarmToggleResult: pushButtonResult, hasPlanResult: hasPlanResult, @@ -156,6 +159,6 @@ final class SettingViewModel: ViewModel { planButtonResult: planButtonResult, alarmButtonResult: alarmButtonResult, loadingViewResult: loadingViewResult, - errorResult: errorResult) + errorResult: totalError) } }