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
90 changes: 70 additions & 20 deletions Wable-iOS.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Wable-iOS/Data/Mapper/CommentMapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ extension CommentMapper {
}

return UserComment(
comment: Comment(
comment: CommentInfo(
author: User(
id: comment.memberID,
nickname: comment.memberNickname,
Expand Down Expand Up @@ -71,7 +71,7 @@ extension CommentMapper {
}

return ContentComment(
comment: Comment(
comment: CommentInfo(
author: User(
id: comment.memberID,
nickname: comment.memberNickname,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Comment.swift
// CommentInfo.swift
// Wable-iOS
//
// Created by 김진웅 on 2/16/25.
Expand All @@ -9,7 +9,7 @@ import Foundation

// MARK: - 댓글 핵심 정보

struct Comment: Identifiable, Hashable {
struct CommentInfo: Identifiable, Hashable {
let author: User
let id: Int
let text: String
Expand All @@ -23,14 +23,14 @@ struct Comment: Identifiable, Hashable {
// MARK: - 유저가 작성한 댓글

struct UserComment: Hashable {
let comment: Comment
let comment: CommentInfo
let contentID: Int
}

// MARK: - 게시물 댓글

struct ContentComment: Hashable {
let comment: Comment
let comment: CommentInfo
let parentID: Int
let isDeleted: Bool
let childs: [ContentComment]
Expand Down
46 changes: 42 additions & 4 deletions Wable-iOS/Domain/Entity/Opacity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,62 @@

import Foundation

// MARK: - 투명도

/// 사용자의 투명도를 관리하는 구조체.
/// 서버에서는 0~-100 사이의 정수로 전달되며, 앱에서는 이를 투명도 백분율과 실제 UI에 적용할 alpha 값으로 변환합니다.
///
/// 사용 예시:
/// ```swift
/// // 서버에서 받은 값으로 초기화
/// let opacity = Opacity(value: -30)
///
/// // 화면에 표시할 백분율 값 (70%)
/// label.text = "\(opacity.displayedValue)%"
///
/// // 실제 UI 요소에 적용할 alpha 값 (0.7)
/// view.alpha = CGFloat(opacity.alpha)
///
/// // 투명도 감소 (신고 등으로 인해)
/// var userOpacity = Opacity(value: -10)
/// userOpacity.reduce() // value가 -11로 변경됨
/// ```
Comment on lines +10 to +27
Copy link
Collaborator

@JinUng41 JinUng41 Mar 14, 2025

Choose a reason for hiding this comment

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

좋은 문서 작성이네요! 👍

struct Opacity: Hashable {
/// 투명도의 최소값 (-85)
/// 이 값보다 낮아질 수 없음
static let minValue: Int = -85

/// 실제 투명도 값 (서버에서 받은 값, 0 ~ -100 사이)
private(set) var value: Int

/// 사용자에게 표시되는 백분율 값 (100% ~ 15%)
/// 서버 값(value)에 100을 더해 양수로 변환
var displayedValue: Int {
return 100 + value
}

/// UI에 실제 적용할 alpha 값 (1.0 ~ 0.15)
/// -81 이하는 0.15로 고정, 나머지는 10단위로 그룹화하여 계단식으로 감소
var alpha: Float {
if value <= -81 {
return 15
return 0.15 // 최소 투명도
}
return (100 + value) / 10 * 10

// 10 단위로 그룹화하여 opacity 설정
// -1~-10: 0.9, -11~-20: 0.8, -21~-30: 0.7 ...
let groupNumber = (abs(value) - 1) / 10 + 1
let opacityValue = 1.0 - (Float(groupNumber) * 0.1)

return max(0.1, opacityValue)
}

/// 투명도 값으로 초기화
/// - Parameter value: 서버에서 받은 투명도 값 (0 ~ -85)
/// 입력 값은 0 ~ minValue 사이로 제한됨
init(value: Int) {
self.value = max(Self.minValue, min(0, value))
}

/// 투명도를 한 단계 감소시키는 메서드 (신고/차단 등으로 인한 감소)
/// minValue에 도달하면 더 이상 감소하지 않음
mutating func reduce() {
if value > Self.minValue {
value -= 1
Expand Down
1 change: 1 addition & 0 deletions Wable-iOS/Domain/Enum/LCKTeam.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Foundation
// MARK: - 2025년 LCK 팀 목록

enum LCKTeam: String {
case lck = "LCK"
case t1 = "T1"
case gen = "GEN"
case hle = "HLE"
Expand Down
18 changes: 18 additions & 0 deletions Wable-iOS/Presentation/Enum/AuthorType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// AuthorType.swift
// Wable-iOS
//
// Created by YOUJIM on 3/18/25.
//


import Foundation

/// 작성자 타입을 정의하는 열거형.
///
/// - `mine`: 내가 작성한 게시물
/// - `others`: 다른 사용자가 작성한 게시물
enum AuthorType {
case mine
case others
}
44 changes: 44 additions & 0 deletions Wable-iOS/Presentation/UIHelper/UIColor+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// UIColor+.swift
// Wable-iOS
//
// Created by YOUJIM on 3/10/25.
//

import UIKit

extension UIColor {

// MARK: - HEX 초기화

/// HEX 코드 문자열로부터 `UIColor` 인스턴스를 생성하는 편의 이니셜라이저.
///
/// - Parameters:
/// - hex: 색상을 나타내는 HEX 문자열(예: "#FFFFFF" 또는 "FFFFFF")
/// - alpha: 색상의 알파값(투명도). 기본값은 1.0(불투명)
///
/// 사용 예시:
/// ```swift
/// let backgroundColor = UIColor("#F5F5F5")
/// let textColor = UIColor("333333", alpha: 0.8)
/// ```
convenience init(_ hex: String, alpha: CGFloat = 1.0) {
var hexFormatted = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()

if hexFormatted.hasPrefix("#") {
hexFormatted = String(hexFormatted.dropFirst())
}

assert(hexFormatted.count == 6, "유효하지 않은 HEX 코드입니다.")
Comment on lines +31 to +32
Copy link
Collaborator

Choose a reason for hiding this comment

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

assert를 사용하신 이유가 있을까요?


var rgbValue: UInt64 = 0
Scanner(string: hexFormatted).scanHexInt64(&rgbValue)

self.init(
red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
alpha: alpha
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//
// CommentButton.swift
// Wable-iOS
//
// Created by YOUJIM on 3/11/25.
//


import UIKit

/// 댓글 버튼을 구현한 커스텀 UIButton 클래스.
/// 게시글에서는 댓글 수를 표시하고, 댓글에서는 '답글쓰기' 텍스트를 표시합니다.
///
/// 사용 예시:
/// ```swift
/// let commentButton = CommentButton()
/// // 게시글용 댓글 버튼 구성
/// commentButton.configureButton(commentCount: 12, type: .content)
/// // 또는 답글 버튼 구성
/// commentButton.configureButton(commentCount: 0, type: .comment)
/// ```
final class CommentButton: UIButton {

// MARK: Property

/// 버튼이 속한 게시물 타입 (게시글/댓글)
private let type: PostType

// MARK: - LifeCycle

init(type: PostType) {
self.type = type

super.init(frame: .zero)

setupConstraint()
}

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

// MARK: - Private Extension

private extension CommentButton {

// MARK: - Setup

func setupConstraint() {
snp.makeConstraints {
$0.adjustedWidthEqualTo(45)
$0.adjustedHeightEqualTo(24)
}
}
}

// MARK: - Configure Extension

extension CommentButton {
/// 댓글 버튼 구성 메서드
/// - Parameters:
/// - commentCount: 댓글 수 (게시글 타입일 때만 사용)
/// - type: 버튼이 속한 게시물 타입 (.content 또는 .comment)
func configureButton(commentCount: Int = 0) {
var configuration = UIButton.Configuration.plain()
var image = UIImage()

switch type {
case .content:
image = .icRipple

configuration.attributedTitle = String(commentCount).pretendardString(with: .caption1)
configuration.baseForegroundColor = .wableBlack
case .comment:
image = .icRippleReply

configuration.attributedTitle = "답글쓰기".pretendardString(with: .caption3)
configuration.baseForegroundColor = .gray600

snp.makeConstraints {
$0.adjustedWidthEqualTo(66)
$0.adjustedHeightEqualTo(20)
}

configuration.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
}

configuration.image = image.withConfiguration(UIImage.SymbolConfiguration(pointSize: 24))
configuration.imagePadding = 4

self.configuration = configuration
}
}
103 changes: 103 additions & 0 deletions Wable-iOS/Presentation/WableComponent/Youjin/Button/GhostButton.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//
// GhostButton.swift
// Wable-iOS
//
// Created by YOUJIM on 3/10/25.
//


import UIKit

/// 내리기 버튼의 크기 타입을 정의하는 열거형.
///
/// - `large`: 텍스트를 포함한 큰 버튼 (71x32)
/// - `small`: 아이콘만 있는 작은 버튼 (32x32)
enum GhostButtonType {
case large
case small
}

/// 내리기 버튼의 상태를 정의하는 열거형.
///
/// - `normal`: 정상 상태 (클릭 가능)
/// - `disabled`: 비활성화 상태 (클릭 불가)
enum GhostButtonStatus {
case normal
case disabled
}

/// 게시물의 투명도를 낮추는 기능을 하는 내리기 버튼 클래스.
/// 두 가지 크기(large/small)와 두 가지 상태(normal/disabled)를 지원합니다.
///
/// 사용 예시:
/// ```swift
/// let ghostButton = GhostButton()
/// // 큰 버튼 구성
/// ghostButton.configureButton(type: .large, status: .normal)
/// // 또는 작은 버튼 구성
/// ghostButton.configureButton(type: .small, status: .disabled)
/// ```
final class GhostButton: UIButton { }

// MARK: - Private Extension

private extension GhostButton {

// MARK: - Setup

/// - Parameter type: 버튼 크기 타입
func setupConstraint(type: GhostButtonType) {
switch type {
case .large:
snp.makeConstraints {
$0.adjustedWidthEqualTo(71)
$0.adjustedHeightEqualTo(32)
}
case .small:
snp.makeConstraints {
$0.adjustedWidthEqualTo(32)
$0.adjustedHeightEqualTo(32)
}
}
}
}

// MARK: - Configure Extension

extension GhostButton {
/// 내리기 버튼 구성 메서드
/// - Parameters:
/// - type: 버튼 크기 타입 (.large 또는 .small)
/// - status: 버튼 상태 (.normal 또는 .disabled)
func configureButton(type: GhostButtonType, status: GhostButtonStatus) {
var configuration = UIButton.Configuration.filled()
self.roundCorners([.all], radius: 16)
self.clipsToBounds = true

switch type {
case .large:
configuration.attributedTitle = "내리기".pretendardString(with: .caption3)
configuration.imagePadding = 4
configuration.imagePlacement = .leading
configuration.contentInsets = NSDirectionalEdgeInsets(top: 6, leading: 10, bottom: 6, trailing: 10)
case .small:
configuration.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8)
}

switch status {
case .normal:
configuration.attributedTitle?.foregroundColor = UIColor("556480")
configuration.image = .icGhostDefault.withTintColor(UIColor("556480"))
configuration.baseBackgroundColor = UIColor("DDE4F1")
self.isUserInteractionEnabled = true
case .disabled:
configuration.attributedTitle?.foregroundColor = UIColor("AEAEAE")
configuration.image = .icGhostDefault.withTintColor(.gray500)
configuration.baseBackgroundColor = UIColor("F7F7F7")
self.isUserInteractionEnabled = false
}

self.configuration = configuration
setupConstraint(type: type)
}
}
Loading