Skip to content
Open
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
18 changes: 14 additions & 4 deletions WSSiOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,19 @@
B9C8AC4D2DDF646800F2076A /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
AGENTS.md,
CLAUDE.md,
Debug.xcconfig,
Info.plist,
Network/AGENTS.md,
Network/CLAUDE.md,
Release.xcconfig,
Resource/AGENTS.md,
Resource/CLAUDE.md,
Source/Data/AGENTS.md,
Source/Data/CLAUDE.md,
Source/Presentation/AGENTS.md,
Source/Presentation/CLAUDE.md,
);
target = 2CD229172B42E685005400BE /* WSSiOS */;
};
Expand Down Expand Up @@ -338,7 +348,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2026061002;
CURRENT_PROJECT_VERSION = 2026062501;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 9SVDHQS4M3;
GENERATE_INFOPLIST_FILE = YES;
Expand All @@ -356,7 +366,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.8.0;
MARKETING_VERSION = 1.9.0;
PRODUCT_BUNDLE_IDENTIFIER = kr.websoso.debug2;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand All @@ -383,7 +393,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2026061002;
CURRENT_PROJECT_VERSION = 2026062501;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 9SVDHQS4M3;
GENERATE_INFOPLIST_FILE = YES;
Expand All @@ -401,7 +411,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.8.0;
MARKETING_VERSION = 1.9.0;
PRODUCT_BUNDLE_IDENTIFIER = kr.websoso;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down
24 changes: 23 additions & 1 deletion WSSiOS/Network/MyLibrary/MyLibraryService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import RxSwift

protocol MyLibraryService {
func getNovelList(userId: Int, queryItem: MyLibraryNovelListQuery) -> Single<MyLibraryListResponse>
func getLibraryKeywords(userId: Int) -> Single<LibraryKeywordListResponse>
}

final class DefaultMyLibraryService: NSObject, Networking {
Expand All @@ -35,7 +36,28 @@ extension DefaultMyLibraryService: MyLibraryService {
.map { try self.decode(data: $0,
to: MyLibraryListResponse.self)}
.asSingle()


} catch {
return Single.error(error)
}
}

func getLibraryKeywords(userId: Int) -> Single<LibraryKeywordListResponse> {
do {
let request = try makeHTTPRequest(
method: .get,
path: URLs.MyLibrary.getLibraryKeywords(userId: userId),
headers: APIConstants.accessTokenHeader,
body: nil
)

NetworkLogger.log(request: request)

return tokenCheckURLSession.rx.data(request: request)
.map { try self.decode(data: $0,
to: LibraryKeywordListResponse.self)}
.asSingle()

} catch {
return Single.error(error)
}
Expand Down
28 changes: 28 additions & 0 deletions WSSiOS/Resource/Constants/Strings/StringLiterals+MyLibrary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,47 @@ extension StringLiterals {
enum FilterButton {
static let interest = "관심"
static let readStatus = "읽기상태"
static let genre = "장르"
static let publicationStatus = "연재상태"
static let starRating = "별점"
static let ratingEmpty = "별점 없음"
static let attractivePoint = "매력포인트"
static let keyword = "키워드"
}

static func novelCountText(_ count: Int) -> String {
return "\(count)개"
}

enum Sort {
static let title = "정렬"
}

enum Filter {
static let title = "작품 찾기 필터"
static let readStatus = "읽기 상태"
static let attractivePoint = "매력포인트"
static let rating = "별점"

// 탭 타이틀
static let tabReadStatus = "읽기상태"
static let tabGenre = "장르"
static let tabPublicationStatus = "연재 상태"
static let tabRating = "별점"
static let tabAttractivePoint = "매력포인트"
static let tabKeyword = "키워드"

// 별점 탭
static let notRatedOnly = "별점 등록 안된 작품만 보기"

// 키워드 탭
static func registeredKeywordCount(_ count: Int) -> String {
if count == 0 {
return "등록된 키워드가 없습니다"
} else {
return "등록한 키워드 \(count)개"
}
}
}

enum Empty {
Expand Down
5 changes: 4 additions & 1 deletion WSSiOS/Resource/Constants/URLs/URLs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,10 @@ enum URLs {
enum MyLibrary {
private static let myLibraryBasePath = "/users"
static func getMyLibrarList(userId: Int) -> String {
return "\(myLibraryBasePath)/\(userId)/novels"
return "\(myLibraryBasePath)/\(userId)/novels/v2"
}
static func getLibraryKeywords(userId: Int) -> String {
return "\(myLibraryBasePath)/\(userId)/novels/keywords"
}
}
}
19 changes: 16 additions & 3 deletions WSSiOS/Resource/Extensions/UIViewController+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -393,12 +393,25 @@ extension UIViewController {
return viewController.filterOption.asObservable()
}

func presentLibraryFilterViewController(_ selectedFilterOption: LibraryFilterOption) -> Observable<LibraryFilterOption> {
let viewController = LibraryFilterViewController(libraryFilterOption: selectedFilterOption)
func presentLibraryFilterViewController(_ selectedFilterOption: LibraryFilterOption,
initialTab: LibraryFilterTab = .readStatus,
repository: MyLibraryRepository) -> Observable<LibraryFilterOption> {
let viewController = LibraryFilterViewController(
viewModel: LibraryFilterViewModel(libraryFilterOption: selectedFilterOption,
initialTab: initialTab,
repository: repository)
)
self.presentModalViewController(viewController)

return viewController.filterOption.asObservable()
}

func presentLibrarySortBottomSheet(_ currentSort: LibrarySortType) -> Observable<LibrarySortType> {
let viewController = LibrarySortBottomSheetViewController(currentSort: currentSort)
self.presentModalViewController(viewController)

return viewController.selectedSort.asObservable()
}

func pushToUserPageFeedDetailViewController(userId: Int, userData: UserProfileEntity) {
let viewController = UserPageFeedDetailViewController(
Expand Down
47 changes: 47 additions & 0 deletions WSSiOS/Source/Data/Base/LibrarySortType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// LibrarySortType.swift
// WSSiOS
//
// Created by YunhakLee on 6/22/26.
//

import Foundation

/// 마이라이브러리 서재 목록 전용 정렬.
/// 공유 타입 `SortType`(RECENT/OLD)과 별개로 분리해, 서재에서만 쓰는 6종 정렬을 다룬다.
enum LibrarySortType: Int, CaseIterable {
case createdDesc
case createdAsc
case title
case date
case ratingDesc
case ratingAsc

/// 사용자 노출 텍스트 (정렬 버튼 라벨 / 바텀시트 행)
var text: String {
switch self {
case .createdDesc: return "등록 최신순"
case .createdAsc: return "등록 오래된순"
case .title: return "제목순"
case .date: return "날짜순"
case .ratingDesc: return "별점 높은순"
case .ratingAsc: return "별점 낮은순"
}
}

/// 서버 v2 sortType 쿼리 토큰 (v2 API 연결 단계에서 사용)
var queryValue: String {
switch self {
case .createdDesc: return "created_desc"
case .createdAsc: return "created_asc"
case .title: return "title" // TODO: 백엔드 토큰 확정 필요 (v2 스펙 미정의)
case .date: return "read_date"
case .ratingDesc: return "rating_desc"
case .ratingAsc: return "rating_asc"
}
}

static func fromText(_ text: String) -> LibrarySortType? {
return LibrarySortType.allCases.first { $0.text == text }
}
}
2 changes: 1 addition & 1 deletion WSSiOS/Source/Data/Base/NovelGenre.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import UIKit

enum NovelGenre: String, CaseIterable {
enum NovelGenre: String, CaseIterable, Codable {
case all = "all"
case fantasy = "fantasy"
case modernFantasy = "modernFantasy"
Expand Down
2 changes: 1 addition & 1 deletion WSSiOS/Source/Data/Base/PublicationStatus.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

enum PublicationStatus: String, CaseIterable {
enum PublicationStatus: String, CaseIterable, Codable {
case onGoing
case completed

Expand Down
12 changes: 12 additions & 0 deletions WSSiOS/Source/Data/DTO/MyLibrary/LibraryKeywordListResponse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// LibraryKeywordListResponse.swift
// WSSiOS
//
// Created by YunhakLee on 6/23/26.
//

import Foundation

struct LibraryKeywordListResponse: Decodable {
let keywords: [KeywordData]
}
48 changes: 31 additions & 17 deletions WSSiOS/Source/Data/DTO/MyLibrary/MyLibraryNovelListQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,55 @@
import Foundation

struct MyLibraryNovelListQuery {
var lastUserNovelId: Int
var size: Int
var sortType: String
var cursor: String? = nil
var isInterest: Bool? = nil
var readStatus: [String]? = nil
var readStatuses: [String]? = nil
var genres: [String]? = nil
var isCompleted: Bool? = nil
var ratingMin: Float? = nil
var ratingMax: Float? = nil
var unratedOnly: Bool? = nil
var attractivePoints: [String]? = nil
var novelRating: Float? = nil
var query: String? = nil
var updatedSince: String? = nil
var keywords: [String]? = nil
}

extension MyLibraryNovelListQuery {
func asQueryItems() -> [URLQueryItem] {
var items: [URLQueryItem] = [
URLQueryItem(name: "lastUserNovelId", value: "\(lastUserNovelId)"),
URLQueryItem(name: "size", value: "\(size)"),
URLQueryItem(name: "sortCriteria", value: sortType)
URLQueryItem(name: "sortType", value: sortType)
]
if let cursor = cursor {
items.append(URLQueryItem(name: "cursor", value: cursor))
}
if let isInterest = isInterest {
items.append(URLQueryItem(name: "isInterest", value: String(isInterest)))
}
if let readStatus = readStatus {
items.append(URLQueryItem(name: "readStatuses", value: readStatus.joined(separator: ",")))
if let readStatuses = readStatuses {
items.append(URLQueryItem(name: "readStatuses", value: readStatuses.joined(separator: ",")))
}
if let attractivePoints = attractivePoints {
items.append(URLQueryItem(name: "attractivePoints", value: attractivePoints.joined(separator: ",")))
if let genres = genres {
items.append(URLQueryItem(name: "genres", value: genres.joined(separator: ",")))
}
if let isCompleted = isCompleted {
items.append(URLQueryItem(name: "isCompleted", value: String(isCompleted)))
}
if let novelRating = novelRating {
items.append(URLQueryItem(name: "novelRating", value: String(novelRating)))
if let ratingMin = ratingMin {
items.append(URLQueryItem(name: "ratingMin", value: String(ratingMin)))
}
if let query = query {
items.append(URLQueryItem(name: "query", value: query))
if let ratingMax = ratingMax {
items.append(URLQueryItem(name: "ratingMax", value: String(ratingMax)))
}
if let unratedOnly = unratedOnly {
items.append(URLQueryItem(name: "unratedOnly", value: String(unratedOnly)))
}
if let attractivePoints = attractivePoints {
items.append(URLQueryItem(name: "attractivePoints", value: attractivePoints.joined(separator: ",")))
}
if let updatedSince = updatedSince {
items.append(URLQueryItem(name: "updatedSince", value: updatedSince))
if let keywords = keywords {
items.append(URLQueryItem(name: "keywords", value: keywords.joined(separator: ",")))
}
return items
}
Expand Down
1 change: 1 addition & 0 deletions WSSiOS/Source/Data/DTO/MyLibrary/MyLibraryResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Foundation
struct MyLibraryListResponse: Decodable {
let userNovelCount: Int
let isLoadable: Bool
let nextCursor: String?
let userNovels: [MyLibraryResponse]
}

Expand Down
2 changes: 1 addition & 1 deletion WSSiOS/Source/Data/DTO/SearchKeywordResult.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ struct KeywordCategory: Codable {
let keywords: [KeywordData]
}

struct KeywordData: Codable {
struct KeywordData: Codable, Equatable {
let keywordId: Int
let keywordName: String
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import UIKit
struct LibraryFilterOption: Equatable, Codable {
var interestedOption: Bool = false
var readStatusOptions: [ReadStatus] = []
var genreOptions: [NovelGenre] = []
var publicationStatusOptions: [PublicationStatus] = []
var minimumStarRateOption: CGFloat = 0.0
var maximumStarRateOption: CGFloat = 5.0
var notStarRatedOption: Bool = false
var attractivePointOptions: [AttractivePoint] = []
var starRatingOption: NovelRatingStatus? = nil
var keywordOptions: [KeywordData] = []
}
2 changes: 2 additions & 0 deletions WSSiOS/Source/Data/Entity/MyLibrary/MyLibraryEntity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import Foundation
struct MyLibraryEntity {
var userNovelCount: Int
var isLoadable: Bool
var nextCursor: String?
var userNovels: [MyLibraryNovel]
}

extension MyLibraryListResponse {
func toEntity() -> MyLibraryEntity {
return MyLibraryEntity(userNovelCount: self.userNovelCount,
isLoadable: self.isLoadable,
nextCursor: self.nextCursor,
userNovels: self.userNovels.map { $0.toEntity() })
}
}
Expand Down
Loading