Skip to content

Commit

Permalink
Merge remote-tracking branch 'refs/remotes/origin/suyeon'
Browse files Browse the repository at this point in the history
Conflicts:
	KkuMulKum.xcodeproj/project.pbxproj
  • Loading branch information
youz2me committed Aug 16, 2024
2 parents 981586d + 3382226 commit 4aefeb9
Show file tree
Hide file tree
Showing 14 changed files with 340 additions and 88 deletions.
17 changes: 17 additions & 0 deletions KkuMulKum.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
780E898C2C5D22B90009D27E /* KakaoSDKTalk in Frameworks */ = {isa = PBXBuildFile; productRef = 780E898B2C5D22B90009D27E /* KakaoSDKTalk */; };
780E898E2C5D22B90009D27E /* KakaoSDKTemplate in Frameworks */ = {isa = PBXBuildFile; productRef = 780E898D2C5D22B90009D27E /* KakaoSDKTemplate */; };
780E89902C5D22B90009D27E /* KakaoSDKUser in Frameworks */ = {isa = PBXBuildFile; productRef = 780E898F2C5D22B90009D27E /* KakaoSDKUser */; };
782965C92C6CCDEF000C7537 /* BottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782965C82C6CCDEF000C7537 /* BottomSheet.swift */; };
782B407B2C3E395A008B0CA7 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782B407A2C3E395A008B0CA7 /* WelcomeView.swift */; };
782B407D2C3E3984008B0CA7 /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782B407C2C3E3984008B0CA7 /* WelcomeViewController.swift */; };
782B407F2C3E44B7008B0CA7 /* WelcomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782B407E2C3E44B7008B0CA7 /* WelcomeViewModel.swift */; };
782B40822C3E4925008B0CA7 /* NicknameViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782B40812C3E4925008B0CA7 /* NicknameViewModel.swift */; };
784824F72C6E1C9900FE07A0 /* AuthServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 784824F62C6E1C9900FE07A0 /* AuthServiceProtocol.swift */; };
784E4D942C3B1C7F00BC943C /* KakaoSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 784E4D932C3B1C7F00BC943C /* KakaoSDK */; };
784E4D962C3B1C7F00BC943C /* KakaoSDKAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 784E4D952C3B1C7F00BC943C /* KakaoSDKAuth */; };
784E4D992C3B95A900BC943C /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 784E4D982C3B95A900BC943C /* KeychainAccess */; };
Expand Down Expand Up @@ -239,10 +241,13 @@
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
782965C82C6CCDEF000C7537 /* BottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomSheet.swift; sourceTree = "<group>"; };
782B407A2C3E395A008B0CA7 /* WelcomeView.swift */ = {isa = PBXFileReference; indentWidth = 5; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = "<group>"; };
782B407C2C3E3984008B0CA7 /* WelcomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = "<group>"; };
782B407E2C3E44B7008B0CA7 /* WelcomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeViewModel.swift; sourceTree = "<group>"; };
782B40812C3E4925008B0CA7 /* NicknameViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameViewModel.swift; sourceTree = "<group>"; };
784824F62C6E1C9900FE07A0 /* AuthServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthServiceProtocol.swift; sourceTree = "<group>"; };
785AE1D02C3B07A600677CA0 /* PrivacyInfo.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = PrivacyInfo.plist; sourceTree = "<group>"; };
789196332C486F6B00FF8CDF /* KeychainAccessible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainAccessible.swift; sourceTree = "<group>"; };
789196352C492F8600FF8CDF /* AuthTargetType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthTargetType.swift; sourceTree = "<group>"; };
789196372C49697B00FF8CDF /* AuthError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthError.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -526,6 +531,14 @@
path = ViewModel;
sourceTree = "<group>";
};
784824F52C6E1C6800FE07A0 /* ServiceProtocol */ = {
isa = PBXGroup;
children = (
784824F62C6E1C9900FE07A0 /* AuthServiceProtocol.swift */,
);
path = ServiceProtocol;
sourceTree = "<group>";
};
789196392C49697F00FF8CDF /* Auth */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1051,6 +1064,7 @@
DD865B652C3920F600C351A2 /* Onboarding */ = {
isa = PBXGroup;
children = (
784824F52C6E1C6800FE07A0 /* ServiceProtocol */,
782B40762C3E389F008B0CA7 /* Welcome */,
DDFA50782C4693BD000A62E2 /* Profile */,
78AED1322C3D9514000AD80A /* Nickname */,
Expand Down Expand Up @@ -1351,6 +1365,7 @@
DE9E187E2C3BA49B00DB76B4 /* Component */ = {
isa = PBXGroup;
children = (
782965C82C6CCDEF000C7537 /* BottomSheet.swift */,
DE9E187F2C3BA4AA00DB76B4 /* CustomButton.swift */,
DE9E18832C3BA84500DB76B4 /* CustomTextField.swift */,
);
Expand Down Expand Up @@ -1753,6 +1768,7 @@
DD1FD0352C5CCF9F00D0A72C /* PromiseInfoViewController.swift in Sources */,
DDAF1C912C3D6E3D008A37D3 /* TardyViewController.swift in Sources */,
782B407D2C3E3984008B0CA7 /* WelcomeViewController.swift in Sources */,
782965C92C6CCDEF000C7537 /* BottomSheet.swift in Sources */,
DE8248002C36E857000601BC /* ObservablePattern.swift in Sources */,
DDAF1C902C3D6E3D008A37D3 /* PromiseViewModel.swift in Sources */,
DE254AAA2C31190E00A4015E /* UIStackView+.swift in Sources */,
Expand Down Expand Up @@ -1850,6 +1866,7 @@
789196342C486F6B00FF8CDF /* KeychainAccessible.swift in Sources */,
DE254AB02C31195B00A4015E /* NSAttributedString+.swift in Sources */,
DD43937B2C412F4500EC1799 /* CreateMeetingViewController.swift in Sources */,
784824F72C6E1C9900FE07A0 /* AuthServiceProtocol.swift in Sources */,
DD86266C2C4606A300E4F980 /* SetReadyInfoViewController.swift in Sources */,
DE8247FD2C36E7C7000601BC /* MoyaLoggingPlugin.swift in Sources */,
DECB845C2C4442490022A003 /* FindPlaceViewController.swift in Sources */,
Expand Down
86 changes: 42 additions & 44 deletions KkuMulKum/Network/Service/AuthService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,22 @@
// Created by 이지훈 on 7/14/24.
//
import Foundation

import Moya

protocol AuthServiceType {
func saveAccessToken(_ token: String) -> Bool
func saveRefreshToken(_ token: String) -> Bool
func getAccessToken() -> String?
func getRefreshToken() -> String?
func clearTokens() -> Bool
func performRequest<T: ResponseModelType>(_ target: AuthTargetType) async throws -> T
enum NetworkErrorMapper {
static func mapErrorResponse(_ error: ErrorResponse) -> NetworkError {
switch error.code {
case 40080:
return .invalidImageFormat
case 40081:
return .imageSizeExceeded
case 40420:
return .userNotFound
default:
return .apiError(code: error.code, message: error.message)
}
}
}

class AuthService: AuthServiceType {
Expand All @@ -26,30 +33,6 @@ class AuthService: AuthServiceType {
self.provider = provider
}

func saveAccessToken(_ token: String) -> Bool {
keychainService.accessToken = token
return keychainService.accessToken == token
}

func saveRefreshToken(_ token: String) -> Bool {
keychainService.refreshToken = token
return keychainService.refreshToken == token
}

func getAccessToken() -> String? {
return keychainService.accessToken
}

func getRefreshToken() -> String? {
return keychainService.refreshToken
}

func clearTokens() -> Bool {
keychainService.accessToken = nil
keychainService.refreshToken = nil
return keychainService.accessToken == nil && keychainService.refreshToken == nil
}

func performRequest<T: ResponseModelType>(_ target: AuthTargetType) async throws -> T {
return try await withCheckedThrowingContinuation { continuation in
provider.request(target) { result in
Expand All @@ -61,7 +44,8 @@ class AuthService: AuthServiceType {
do {
let decodedResponse = try JSONDecoder().decode(ResponseBodyDTO<T>.self, from: response.data)
guard decodedResponse.success else {
throw decodedResponse.error.map(self.mapErrorResponse) ?? NetworkError.unknownError("Unknown error occurred")
throw decodedResponse.error.map(NetworkErrorMapper.mapErrorResponse) ??
NetworkError.unknownError("Unknown error occurred")
}
guard let data = decodedResponse.data else {
if T.self == EmptyModel.self {
Expand All @@ -78,21 +62,35 @@ class AuthService: AuthServiceType {
case .failure(let error):
continuation.resume(throwing: NetworkError.networkError(error))
}

}
}
}
}


extension AuthService {
func saveAccessToken(_ token: String) -> Bool {
keychainService.accessToken = token
return keychainService.accessToken == token
}

private func mapErrorResponse(_ error: ErrorResponse) -> NetworkError {
switch error.code {
case 40080:
return .invalidImageFormat
case 40081:
return .imageSizeExceeded
case 40420:
return .userNotFound
default:
return .apiError(code: error.code, message: error.message)
}
func saveRefreshToken(_ token: String) -> Bool {
keychainService.refreshToken = token
return keychainService.refreshToken == token
}

func getAccessToken() -> String? {
return keychainService.accessToken
}

func getRefreshToken() -> String? {
return keychainService.refreshToken
}

func clearTokens() -> Bool {
keychainService.accessToken = nil
keychainService.refreshToken = nil
return keychainService.accessToken == nil && keychainService.refreshToken == nil
}
}
196 changes: 196 additions & 0 deletions KkuMulKum/Resource/Component/BottomSheet.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
//
// UIBottomSheet+.swift
// KkuMulKum
//
// Created by 이지훈 on 8/14/24.
//// ref: https://velog.io/@minsang/iOS-Bottom-Sheet

import UIKit
import SnapKit
import Then

enum BottomSheetViewState {
case expanded
case normal
}

class BottomSheetViewController: BaseViewController {
private lazy var dimmedView = UIView().then {
$0.backgroundColor = UIColor.darkGray.withAlphaComponent(self.dimmedAlpha)
$0.alpha = 0
}

private lazy var bottomSheetView = UIView().then {
$0.backgroundColor = .white
$0.layer.cornerRadius = self.cornerRadius
$0.layer.cornerCurve = .continuous
$0.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
$0.clipsToBounds = true
}

private let dragIndicatorView = UIView().then {
$0.backgroundColor = .black
$0.tintColor = .black
$0.layer.cornerRadius = 1.5
$0.alpha = 0
}

let defaultHeight: CGFloat
// bottomSheetView의 상단 CornerRadius 값
let cornerRadius: CGFloat
// dimmedView의 alpha값
let dimmedAlpha: CGFloat
// pannedGesture 활성화 여부
let isPannedable: Bool

// Bottom Sheet과 safe Area Top 사이의 최소값을 지정하기 위한 프로퍼티
private let bottomSheetPanMinTopConstant: CGFloat = 40
// 드래그 하기 전에 Bottom Sheet의 top Constraint value를 저장하기 위한 프로퍼티
private lazy var bottomSheetPanStartingTopConstant: CGFloat = bottomSheetPanMinTopConstant

private let contentViewController: UIViewController

init(
contentViewController: UIViewController,
defaultHeight: CGFloat,
cornerRadius: CGFloat = 8,
dimmedAlpha: CGFloat = 0.6,
isPannedable: Bool = false
) {
self.contentViewController = contentViewController
self.defaultHeight = defaultHeight
self.cornerRadius = cornerRadius
self.dimmedAlpha = dimmedAlpha
self.isPannedable = isPannedable

super.init(nibName: nil, bundle: nil)
self.modalPresentationStyle = .overFullScreen
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func viewDidLoad() {
super.viewDidLoad()

self.configureUI()
self.configureLayout()
self.configureDimmedTapGesture()
self.dragIndicatorView.alpha = 1
self.bottomSheetView.transform = CGAffineTransform(translationX: 0, y: self.defaultHeight)
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.showBottomSheet()
}

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate { [weak self] _ in
self?.showBottomSheet()
}
}

override func setupView() {
super.setupView()

view.backgroundColor = .clear
configureUI()
configureLayout()
self.dragIndicatorView.alpha = 1
self.bottomSheetView.transform = CGAffineTransform(translationX: 0, y: self.defaultHeight)
}

private func configureUI() {
view.addSubviews(dimmedView, bottomSheetView, dragIndicatorView)
addChild(contentViewController)
bottomSheetView.addSubview(contentViewController.view)
bottomSheetView.addSubview(dragIndicatorView)
contentViewController.didMove(toParent: self)
dragIndicatorView.backgroundColor = .black
}

private func showBottomSheet(atState: BottomSheetViewState = .normal) {
let screenHeight: CGFloat = view.frame.height

if atState == .normal {
bottomSheetView.snp.updateConstraints {
$0.height.equalTo(defaultHeight)
}
} else {
bottomSheetView.snp.updateConstraints {
$0.height.equalTo(screenHeight - bottomSheetPanMinTopConstant)
}
}

UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut, animations: {
self.dimmedView.alpha = self.dimmedAlpha
self.bottomSheetView.transform = .identity
self.view.layoutIfNeeded()
}, completion: nil)
}

}

// MARK: Configure
extension BottomSheetViewController {
private func configureLayout() {
dimmedView.snp.makeConstraints {
$0.edges.equalToSuperview()
}

bottomSheetView.snp.makeConstraints {
$0.leading.trailing.bottom.equalTo(view.safeAreaLayoutGuide)
$0.bottom.equalToSuperview()
$0.height.equalTo(defaultHeight)
}

contentViewController.view.snp.makeConstraints {
$0.edges.equalToSuperview()
}

dragIndicatorView.snp.makeConstraints {
$0.width.equalTo(60)
$0.height.equalTo(3)
$0.centerX.equalTo(view.safeAreaLayoutGuide)
$0.bottom.equalTo(bottomSheetView.snp.top).offset(12)
}
}

private func configureDimmedTapGesture() {
let dimmedTap = UITapGestureRecognizer(target: self, action: #selector(dimmedViewTapped(_:)))
dimmedView.addGestureRecognizer(dimmedTap)
dimmedView.isUserInteractionEnabled = true
}
}

// MARK: Gesture
extension BottomSheetViewController {
@objc private func dimmedViewTapped(_ tapRecognizer: UITapGestureRecognizer) {
self.hideBottomSheetAndGoBack()
}
}

extension BottomSheetViewController {
private func hideBottomSheetAndGoBack() {
let hideTransform = CGAffineTransform(translationX: 0, y: self.defaultHeight)

UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseIn, animations: {
self.dimmedView.alpha = 0.0
self.bottomSheetView.transform = hideTransform
self.view.layoutIfNeeded()
}) { _ in
if self.presentingViewController != nil {
self.dismiss(animated: false, completion: nil)
}
}
}

private func nearest(to number: CGFloat, inValues values: [CGFloat]) -> CGFloat {
guard let nearestVal = values.min(by: { abs(number - $0) < abs(number - $1) })
else { return number }
return nearestVal
}
}
Loading

0 comments on commit 4aefeb9

Please sign in to comment.