Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,9 @@ public struct DataSourceDependencyAssembler: DependencyAssemblerProtocol {
DIContainer.shared.register(type: OnboardingRepositoryProtocol.self) { _ in
return OnboardingRepository(networkService: networkService, keychainStorage: keychainStorage)
}

DIContainer.shared.register(type: UserDataRepositoryProtocol.self) { _ in
return UserDataRepository(keychainStorage: keychainStorage, userDefaultsStorage: userDefaultsStorage)
}
}
}
24 changes: 24 additions & 0 deletions Projects/DataSource/Sources/Common/Error/UserError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// UserError.swift
// DataSource
//
// Created by 이동현 on 7/20/25.
//

enum UserError: Error, CustomStringConvertible {
case accessTokenLoadFailed
case nicknameLoadFailed
case unknown(error: Error)


var description: String {
switch self {
case .accessTokenLoadFailed:
return "토큰 불러오기에 실패했습니다."
case .nicknameLoadFailed:
return "닉네임 불러오기에 실패했습니다."
case .unknown(let error):
return "알 수 없는 에러가 발생했습니다. \(error.localizedDescription)"
}
}
}
37 changes: 37 additions & 0 deletions Projects/DataSource/Sources/Repository/UserDataRepository.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// UserDataRepository.swift
// DataSource
//
// Created by 이동현 on 7/20/25.
//

import Domain
import Foundation

final class UserDataRepository: UserDataRepositoryProtocol {
private let keychainStorage: KeychainStorageProtocol
private let userDefaultsStorage: UserDefaultsStorageProtocol

init(keychainStorage: KeychainStorageProtocol, userDefaultsStorage: UserDefaultsStorageProtocol) {
self.keychainStorage = keychainStorage
self.userDefaultsStorage = userDefaultsStorage
}

// TODO: - accessToken fetch 로직 상의 후 결정
func loadAccessToken() throws -> String {
guard let token = keychainStorage.load(forKey: TokenType.accessToken.rawValue) else {
throw UserError.accessTokenLoadFailed
}

return token
}

func loadNickname() throws -> String {
guard let nickname: String = userDefaultsStorage.load(forKey: UserDefaultsKey.nickname.rawValue) else {
throw UserError.nicknameLoadFailed
}

return nickname
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// UserDataRepositoryProtocol.swift
// DataSource
//
// Created by 이동현 on 7/20/25.
//

/// 유저의 이름, 토큰 등 유저 정보와 관련된 데이터를 가져오는 Repository
public protocol UserDataRepositoryProtocol {
/// 저장한 accessToken을 가져옵니다.
/// - Returns: accessToken
func loadAccessToken() throws -> String

/// 저장한 닉네임을 가져옵니다.
/// - Returns: 유저 닉네임
func loadNickname() throws -> String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "chevron_right_icon@1x.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "chevron_right_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "chevron_right_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "setting_icon@1x.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "setting_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "setting_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ enum BitnagilIcon {

static let mypageFillIcon = UIImage(named: "mypage_fill_icon", in: bundle, with: nil)
static let mypageEmptyIcon = UIImage(named: "mypage_empty_icon", in: bundle, with: nil)

static let settingIcon = UIImage(named: "setting_icon", in: bundle, with: nil)

static let chevronRightIcon = UIImage(named: "chevron_right_icon", in: bundle, with: nil)
}

enum Direction {
Expand Down
18 changes: 18 additions & 0 deletions Projects/Presentation/Sources/Common/Extension/NSObject+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// NSObject+.swift
// Presentation
//
// Created by 이동현 on 7/20/25.
//

import Foundation

extension NSObject {
var className: String {
return String(describing: type(of: self))
}

class var className: String {
return String(describing: self)
}
Comment on lines +11 to +17

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요 className 너무너무 좋은 것 같아요 !!
테이블 뷰 셀 정의할 때 하드코딩 안하고 이렇게 하는 고지능적 개발 !!! 감사합니다.

다만 궁금한게 var classNameclass var className가 어떻게 다른지 알 수 있을까요 ??
둘 다 사용해야만 className을 빼올 수 있는건가용 ??

제가 feat/myPage 브랜치에서 다음 부분을 주석처리해도 마이페이지가 잘 작동하는 것 같아 궁금해서 질문 남겨봅니다 !!!

 var className: String {
    return String(describing: type(of: self))
}

추가적으로 이 extension이 public인 이유도 궁금합니다 !!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

우선 public일 필요는 전혀 없습니다!! 추가의 이뉴는 아시다시피 UIView가 NSObject의 서브 클래스이기 때문에 UIView 서브 클래스들의 클래스 면 사용을 편하게 하기 위해서 인데요!! 때문에 Presentation 모듈 밖에서 사용할 일이 없으니 떼는게 좋을거 같아요!! 진짜 꼼꼼하게 봐주셨군요 감사합니다!!!

두 변수의 차이점은 인스턴스 변수냐, 클래스 변수냐의 차이입니다.

  • var class 의 경우, 인스턴스가 존재할 때 해당 인스턴스의 클래스 이름을 불러올 필요가 있을때 사용하시면 되구
  • class var의 경우, 타입 자체의 이름을 String으로 불러올 때 사용하시면 좋을 것 같습니다!

아직은 두 번째 class var 만 사용하고 있지만, 정말 추후 혹시나 인스턴스의 클래스 이름을 String으로 빼오는 경우도 고려해서 한 번에 추가해 두었습니다!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

헐 ~ 바로 이해됩니다 .... 띵천재 띵고수 😎
많이 배워갑니다 ~~

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,12 @@ public struct PresentationDependencyAssembler: DependencyAssemblerProtocol {
DIContainer.shared.register(type: RecommendedRoutineViewModel.self) { _ in
return RecommendedRoutineViewModel()
}

DIContainer.shared.register(type: MypageViewModel.self) { container in
guard let userDataRepository = container.resolve(type: UserDataRepositoryProtocol.self)
else { fatalError("userDataRepository 의존성이 등록되지 않았습니다.") }

return MypageViewModel(userDataRepository: userDataRepository)
}
}
}
15 changes: 7 additions & 8 deletions Projects/Presentation/Sources/Common/TabBarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,19 @@ final public class TabBarView: UITabBarController {
tabBar.tintColor = BitnagilColor.navy600
tabBar.unselectedItemTintColor = BitnagilColor.navy100


guard let viewModel = DIContainer.shared.resolve(type: RecommendedRoutineViewModel.self) else {
fatalError("RecommendedViewModel 의존성이 등록되지 않았습니다.")
}

guard let mypageViewModel = DIContainer.shared.resolve(type: MypageViewModel.self) else {
fatalError("mypageViewModel 의존성이 등록되지 않았습니다.")
}

let homeView = HomeView()
let recommendView = RecommendedRoutineView(viewModel: viewModel)
let reportView = ReportView()
let mypageView = MypageView()
let mypageView = MypageView(viewModel: mypageViewModel)

homeView.tabBarItem = UITabBarItem(
title: "홈",
Expand Down Expand Up @@ -89,10 +95,3 @@ final class ReportView: UIViewController {
view.backgroundColor = .white
}
}

final class MypageView: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// MyPageTableViewCell.swift
// Presentation
//
// Created by 이동현 on 7/17/25.
//

import SnapKit
import UIKit

final class MypageTableViewCell: UITableViewCell {
private enum Layout {
static let titleLableLeadingSpacing: CGFloat = 20
static let titleLableTrailingSpacing: CGFloat = 8
static let chevronImageViewTrailingSpacing: CGFloat = 6
static let chevronImageViewSize: CGFloat = 36
}

private let titleLabel = UILabel()
private let chevronImageView = UIImageView()

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
configureAttribute()
configureLayout()
}

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

func configure(title: String) {
titleLabel.text = title
}

private func configureAttribute() {
titleLabel.font = BitnagilFont(style: .body1, weight: .regular).font

chevronImageView.image = BitnagilIcon.chevronRightIcon
}

private func configureLayout() {
contentView.addSubview(titleLabel)
contentView.addSubview(chevronImageView)

chevronImageView.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.trailing.equalToSuperview().inset(Layout.chevronImageViewTrailingSpacing)
make.size.equalTo(Layout.chevronImageViewSize)
}

titleLabel.snp.makeConstraints { make in
make.verticalEdges.equalToSuperview()
make.leading.equalToSuperview().offset(Layout.titleLableLeadingSpacing)
make.trailing.equalTo(chevronImageView.snp.leading).offset(-Layout.titleLableTrailingSpacing)
}
}
}
Loading
Loading