diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index 1b60d6d..10343a7 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -16,12 +16,8 @@ jobs: - name: Set Xcode version run: sudo xcode-select -s /Applications/Xcode_16.4.app - - name: Install Mise and Tuist - run: | - curl -fsSL https://mise.jdx.dev/install.sh | sh - export PATH="$HOME/.local/bin:$PATH" - mise install tuist - mise use -g tuist + - name: Install Tuist + run: brew install tuist - name: Generate xcconfig run: | @@ -32,9 +28,8 @@ jobs: - name: Generate Xcode project with Tuist run: | - export PATH="$HOME/.local/bin:$PATH" - mise exec -- tuist install - mise exec -- tuist generate --no-open + tuist install + tuist generate --no-open - name: Build with xcodebuild run: | @@ -43,6 +38,5 @@ jobs: -workspace Bitnagil.xcworkspace \ -scheme App \ -sdk iphonesimulator \ - -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.4' \ - clean build | xcpretty - + -destination 'generic/platform=iOS Simulator' \ + clean build diff --git a/Projects/Presentation/Sources/Common/Component/PrimaryButton.swift b/Projects/Presentation/Sources/Common/Component/PrimaryButton.swift index c4aeef8..d7b4ffa 100644 --- a/Projects/Presentation/Sources/Common/Component/PrimaryButton.swift +++ b/Projects/Presentation/Sources/Common/Component/PrimaryButton.swift @@ -43,6 +43,7 @@ final class PrimaryButton: UIButton { self.buttonState = buttonState super.init(frame: .zero) configureAttribute(buttonTitle: buttonTitle) + self.isEnabled = buttonState != .disabled } required init?(coder: NSCoder) { diff --git a/Projects/Presentation/Sources/Withdraw/View/WithdrawViewController.swift b/Projects/Presentation/Sources/Withdraw/View/WithdrawViewController.swift index d2f7dcc..bd1d7cd 100644 --- a/Projects/Presentation/Sources/Withdraw/View/WithdrawViewController.swift +++ b/Projects/Presentation/Sources/Withdraw/View/WithdrawViewController.swift @@ -40,7 +40,7 @@ final class WithdrawViewController: BaseViewController { private let withdrawReasonView = UIView() private let withdrawReasonLabel = UILabel() private let withdrawReasonStackView = UIStackView() - private var withdrawButtons: [WithdrawReason: BitnagilChoiceButton] = [:] + private var withdrawReasonButtons: [WithdrawReason: BitnagilChoiceButton] = [:] private let withdrawReasonTextBackgroundView = UIView() private let withdrawReasonTextViewPlaceholder = UILabel() private let withdrawReasonTextView = UITextView() @@ -63,6 +63,11 @@ final class WithdrawViewController: BaseViewController { } } + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + removeKeyboardNotification() + } + private func updateConstraint() { let height = view.bounds.height if height <= 667 { @@ -121,7 +126,7 @@ final class WithdrawViewController: BaseViewController { make.height.equalTo(Layout.withdrawChoiceButtonHeight) } withdrawReasonStackView.addArrangedSubview(withdrawChoiceButton) - withdrawButtons[withdrawReason] = withdrawChoiceButton + withdrawReasonButtons[withdrawReason] = withdrawChoiceButton } withdrawReasonTextBackgroundView.backgroundColor = BitnagilColor.gray99 @@ -147,6 +152,12 @@ final class WithdrawViewController: BaseViewController { self?.viewModel.action(input: .withdrawService) }, for: .touchUpInside) + + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) + tapGesture.cancelsTouchesInView = false + view.addGestureRecognizer(tapGesture) + + configureKeyboardNotification() } override func configureLayout() { @@ -284,7 +295,7 @@ final class WithdrawViewController: BaseViewController { } private func updateWithdrawReason(selectedWithdrawReason: WithdrawReason?) { - withdrawButtons.forEach { withdrawReason in + withdrawReasonButtons.forEach { withdrawReason in let isSelected = withdrawReason.key == selectedWithdrawReason withdrawReason.value.updateButtonState(isChecked: isSelected) } @@ -296,11 +307,74 @@ final class WithdrawViewController: BaseViewController { withdrawReasonMaxLengthLabel.isHidden = true } } + + private func configureKeyboardNotification() { + NotificationCenter.default.addObserver( + self, + selector: #selector(keyboardWillAppear), + name: UIResponder.keyboardWillShowNotification, + object: nil) + + NotificationCenter.default.addObserver( + self, + selector: #selector(keyboardWillDisappear), + name: UIResponder.keyboardWillHideNotification, + object: nil) + } + + private func removeKeyboardNotification() { + NotificationCenter.default.removeObserver( + self, + name: UIResponder.keyboardWillShowNotification, + object: nil) + + NotificationCenter.default.removeObserver( + self, + name: UIResponder.keyboardWillHideNotification, + object: nil) + } + + @objc private func dismissKeyboard() { + view.endEditing(true) + } + + @objc private func keyboardWillAppear(_ sender: Notification) { + guard + let keyboardFrame = sender.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect, + let duration = sender.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double + else { return } + + let keyboardHeight = keyboardFrame.height + let buttonFrame = withdrawButton.convert(withdrawButton.bounds, to: view) + let buttonBottom = buttonFrame.maxY + let visibleHeight = view.frame.height - keyboardHeight + + if buttonBottom > visibleHeight { + let offset = buttonBottom - visibleHeight + 50 + + UIView.animate(withDuration: duration) { + self.view.frame.origin.y = -offset + } + } + } + + @objc private func keyboardWillDisappear(_ sender: Notification) { + guard let duration = sender.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double + else { return } + + UIView.animate(withDuration: duration) { + self.view.frame.origin.y = 0 + } + } } extension WithdrawViewController: UITextViewDelegate { func textViewDidBeginEditing(_ textView: UITextView) { viewModel.action(input: .choiceWithdrawReason(reason: nil)) + + if !textView.text.isEmpty { + viewModel.action(input: .inputWithdrawReason(reason: textView.text)) + } } func textViewDidChange(_ textView: UITextView) {