Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 1 addition & 1 deletion Animal-Crossing-Wiki/Configurations/TargetVersion.xcconfig
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// 배포 버전 제어용. 워크플로우에서 TARGET_VERSION 값을 덮어씁니다.
TARGET_VERSION = 3.0.1
TARGET_VERSION = 3.1.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "icon-hoteltickets.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"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.
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,8 @@ Find the villagers you have visited and tap the home icon on the villager's page
"Redd's Co-op Raffle" = "Redd's Co-op Raffle";
"Wilbur" = "Wilbur";
"Kapp'n" = "Kapp'n";
"Hotel Souvenir Shop" = "Hotel Souvenir Shop";
"Slumber Islands" = "Slumber Islands";

// MARK: - Source note
"Source Note" = "Source Note";
Expand Down Expand Up @@ -703,6 +705,9 @@ Find the villagers you have visited and tap the home icon on the villager's page
"japanese style" = "Japanese Style";
"animal" = "Animal";
"haniwa" = "Haniwa";
"splatoon" = "Splatoon";
"lego" = "LEGO";
"zelda" = "Zelda";

// MARK: - Housewares - Concept
"ancient" = "ancient";
Expand Down Expand Up @@ -1577,3 +1582,11 @@ Find the villagers you have visited and tap the home icon on the villager's page
"patternLargespike" = "📈 Large Spike";
"patternSmallspike" = "📉 Small Spike";
"patternDecreasing" = "👎 Decreasing";

// MARK: - Exchange Currency
"Heart Crystals" = "Heart Crystals";
"Nook Miles" = "Nook Miles";
"Poki" = "Poki";
"Nook Points" = "Nook Points";
"Bells" = "Bells";
"Hotel Tickets" = "Hotel Tickets";
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,8 @@
"Redd's Co-op Raffle" = "여욱 노점";
"Wilbur" = "로드리";
"Kapp'n" = "갑돌";
"Hotel Souvenir Shop" = "호텔 기념품 가게";
"Slumber Islands" = "슬럼버 아일랜드";

// MARK: - Source note
"Source Note" = "메모";
Expand Down Expand Up @@ -704,6 +706,9 @@
"japanese style" = "동양풍";
"animal" = "동물";
"haniwa" = "토용";
"splatoon" = "스플래툰";
"lego" = "레고";
"zelda" = "젤다";

// MARK: - Housewares - Concept
"ancient" = "유물";
Expand Down Expand Up @@ -1582,3 +1587,11 @@
"patternLargespike" = "📈 큰폭 상승";
"patternSmallspike" = "📉 작은폭 상승";
"patternDecreasing" = "👎 계속 하락";

// MARK: - Exchange Currency
"Heart Crystals" = "하트 크리스탈";
"Nook Miles" = "너굴 마일";
"Poki" = "포키";
"Nook Points" = "너굴 포인트";
"Bells" = "벨";
"Hotel Tickets" = "호텔 티켓";
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,23 @@ final class CoreDataStorage {

lazy var persistentContainer: NSPersistentCloudKitContainer = {
let container = NSPersistentCloudKitContainer(name: "CoreDataStorage")

// Automatic lightweight migration 설정 (새 entity 추가 시 자동 마이그레이션)
container.persistentStoreDescriptions.forEach { description in
description.setOption(true as NSNumber, forKey: NSMigratePersistentStoresAutomaticallyOption)
description.setOption(true as NSNumber, forKey: NSInferMappingModelAutomaticallyOption)
}

container.loadPersistentStores(completionHandler: { (_, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})

// Background context와 viewContext 자동 병합 설정
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy

return container
}()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,15 @@
<relationship name="critters" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="ItemEntity" inverseName="userColletion" inverseEntity="ItemEntity"/>
<relationship name="dailyTasks" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="DailyTaskEntity" inverseName="userCollection" inverseEntity="DailyTaskEntity"/>
<relationship name="npcLike" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="NPCLikeEntity" inverseName="userCollection" inverseEntity="NPCLikeEntity"/>
<relationship name="variants" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="VariantCollectionEntity" inverseName="userCollection" inverseEntity="VariantCollectionEntity"/>
<relationship name="villagersHouse" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="VillagersHouseEntity" inverseName="userCollection" inverseEntity="VillagersHouseEntity"/>
<relationship name="villagersLike" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="VillagersLikeEntity" inverseName="userCollection" inverseEntity="VillagersLikeEntity"/>
</entity>
<entity name="VariantCollectionEntity" representedClassName="VariantCollectionEntity" syncable="YES" codeGenerationType="class">
<attribute name="itemName" optional="YES" attributeType="String"/>
<attribute name="variantId" attributeType="String"/>
<relationship name="userCollection" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserCollectionEntity" inverseName="variants" inverseEntity="UserCollectionEntity"/>
</entity>
<entity name="VillagersHouseEntity" representedClassName="VillagersHouseEntity" syncable="YES" codeGenerationType="class">
<attribute name="birthday" attributeType="String"/>
<attribute name="catchphrase" attributeType="String"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ final class CoreDataItemsStorage: ItemsStorage {
let newItem = ItemEntity(item, context: context)
object.addToCritters(newItem)
}

context.saveContext()
} catch {
debugPrint(error)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
//
// CoreDataVariantsStorage.swift
// Animal-Crossing-Wiki
//
// Created by Claude Code on 2026/02/01.
//

import Foundation
import RxSwift

final class CoreDataVariantsStorage: VariantsStorage {

private let coreDataStorage: CoreDataStorage

init(coreDataStorage: CoreDataStorage = CoreDataStorage.shared) {
self.coreDataStorage = coreDataStorage
}

func fetch() -> Single<Set<String>> {
return Single.create { single in
self.coreDataStorage.performBackgroundTask { context in
do {
let object = try self.coreDataStorage.getUserCollection(context)
let variantEntities = object.variants?.allObjects as? [VariantCollectionEntity] ?? []
let variantIds = Set(variantEntities.compactMap { $0.variantId })
single(.success(variantIds))
} catch {
single(.failure(CoreDataStorageError.readError(error)))
}
}
return Disposables.create()
}
}

func fetchByItem(_ itemName: String) -> Single<Set<String>> {
return Single.create { single in
self.coreDataStorage.performBackgroundTask { context in
do {
let object = try self.coreDataStorage.getUserCollection(context)
let variantEntities = object.variants?.allObjects as? [VariantCollectionEntity] ?? []
let variantIds = Set(variantEntities
.filter { $0.itemName == itemName }
.compactMap { $0.variantId })
single(.success(variantIds))
} catch {
single(.failure(CoreDataStorageError.readError(error)))
}
}
return Disposables.create()
}
}

func fetchAll() -> Single<[String: Set<String>]> {
return Single.create { single in
self.coreDataStorage.performBackgroundTask { context in
do {
let object = try self.coreDataStorage.getUserCollection(context)
let variantEntities = object.variants?.allObjects as? [VariantCollectionEntity] ?? []

var variantsByItem: [String: Set<String>] = [:]
for entity in variantEntities {
guard let itemName = entity.itemName, let variantId = entity.variantId else { continue }
variantsByItem[itemName, default: []].insert(variantId)
}

single(.success(variantsByItem))
} catch {
single(.failure(CoreDataStorageError.readError(error)))
}
}
return Disposables.create()
}
}

func add(_ variantId: String, itemName: String) {
coreDataStorage.performBackgroundTask { context in
do {
let object = try self.coreDataStorage.getUserCollection(context)
let variants = object.variants?.allObjects as? [VariantCollectionEntity] ?? []

if variants.contains(where: { $0.variantId == variantId }) {
return
}

let newVariant = VariantCollectionEntity(context: context)
newVariant.variantId = variantId
newVariant.itemName = itemName
object.addToVariants(newVariant)

context.saveContext()
} catch {
debugPrint(error)
}
}
}

func remove(_ variantId: String) {
coreDataStorage.performBackgroundTask { context in
do {
let object = try self.coreDataStorage.getUserCollection(context)
let variants = object.variants?.allObjects as? [VariantCollectionEntity] ?? []

if let variant = variants.first(where: { $0.variantId == variantId }) {
object.removeFromVariants(variant)
context.delete(variant)
context.saveContext()
}
} catch {
debugPrint(error)
}
}
}

func removeAll(for itemName: String) {
coreDataStorage.performBackgroundTask { context in
do {
let object = try self.coreDataStorage.getUserCollection(context)
let allVariants = object.variants?.allObjects as? [VariantCollectionEntity] ?? []
let variantsToRemove = allVariants.filter { $0.itemName == itemName }

variantsToRemove.forEach { variant in
object.removeFromVariants(variant)
context.delete(variant)
}

context.saveContext()
} catch {
debugPrint(error)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// VariantsStorage.swift
// Animal-Crossing-Wiki
//
// Created by Claude Code on 2026/02/01.
//

import Foundation
import RxSwift

protocol VariantsStorage {
func fetch() -> Single<Set<String>>
func fetchByItem(_ itemName: String) -> Single<Set<String>>
func fetchAll() -> Single<[String: Set<String>]>
func add(_ variantId: String, itemName: String)
func remove(_ variantId: String)
func removeAll(for itemName: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ extension Item {
exchangeCurrency == .poki || variations?.first?.exchangeCurrency == .poki
}

var canExchangeHotelTickets: Bool {
exchangeCurrency == .hotelTickets || variations?.first?.exchangeCurrency == .hotelTickets
}

var isCritters: Bool {
Category.critters.contains(category)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ struct WardrobeVariat: Decodable {
let colors: [Color]

func toVariat() -> Variant {
let generatedVariantId = "\(filename)_\(internalId)_0"

return .init(
image: closetImage ?? storageImage,
variation: nil,
Expand All @@ -79,7 +81,7 @@ struct WardrobeVariat: Decodable {
seasonEventExclusive: seasonEventExclusive,
hhaCategory: nil,
filename: filename,
variantId: "1_0_0",
variantId: generatedVariantId,
internalId: internalId,
variantTranslations: variantTranslations,
colors: colors,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ enum ExchangeCurrency: String, Codable {
case poki = "Poki"
case nookPoints = "Nook Points"
case bells = "Bells"
case hotelTickets = "Hotel Tickets"
}

enum HhaCategory: String, Codable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation

enum EnvironmentsVariable {
static let repoURL = "https://raw.githubusercontent.com/leeari95/animal-crossing/master/json/data/"
static let repoURL = "https://raw.githubusercontent.com/leeari95/animal-crossing/release/3.0.0/json/data/"
static let turnupURL = "https://api.ac-turnip.com/data/"
static let acnhAPI = "https://acnhapi.com/v1/"
}
Loading
Loading