Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[동물] NPC에 종족과 출현장소를 추가 #83

Merged
merged 8 commits into from
Nov 30, 2024
289 changes: 239 additions & 50 deletions Animal-Crossing-Wiki/Projects/App/Resources/en.lproj/Localizable.strings

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,14 @@
<relationship name="userColletion" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserCollectionEntity" inverseName="critters" inverseEntity="UserCollectionEntity"/>
</entity>
<entity name="NPCLikeEntity" representedClassName="NPCLikeEntity" syncable="YES" codeGenerationType="class">
<attribute name="appearanceLocation" optional="YES" attributeType="Transformable" customClassName="[Data]"/>
<attribute name="birthday" attributeType="String"/>
<attribute name="gender" attributeType="String"/>
<attribute name="genderAsia" attributeType="String"/>
<attribute name="iconImage" attributeType="String"/>
<attribute name="name" attributeType="String"/>
<attribute name="photoImage" attributeType="String"/>
<attribute name="species" optional="YES" attributeType="String"/>
<attribute name="translations" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromData" customClassName="[String: String]"/>
<relationship name="userCollection" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserCollectionEntity" inverseName="npcLike" inverseEntity="UserCollectionEntity"/>
</entity>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,20 @@ final class CoreDataNPCLikeStorage: NPCLikeStorage {
func fetch() -> [NPC] {
let context = coreDataStorage.persistentContainer.viewContext
let object = try? self.coreDataStorage.getUserCollection(context)
let npc = object?.npcLike?.allObjects as? [NPCLikeEntity] ?? []
return npc.map { $0.toDomain() }
.sorted(by: { $0.translations.localizedName() < $1.translations.localizedName() })
let npcs = object?.npcLike?.allObjects as? [NPCLikeEntity] ?? []
return npcs.map { object -> NPC in
var appearanceLocation: [AppearanceLocation] = []

if let dataList = object.appearanceLocation {
let decodedList = dataList.compactMap { data -> AppearanceLocation? in
try? JSONDecoder().decode(AppearanceLocation.self, from: data)
}
appearanceLocation.append(contentsOf: decodedList)
}

return object.toDomain(appearanceLocation: appearanceLocation)
}
.sorted(by: { $0.translations.localizedName() < $1.translations.localizedName() })
}

func update(_ npc: NPC) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,26 @@ extension NPCLikeEntity {
self.birthday = npc.birthday
self.gender = npc.gender.rawValue
self.genderAsia = npc.genderAsia.rawValue
self.species = npc.species
self.iconImage = npc.iconImage
self.photoImage = npc.photoImage
self.name = npc.name
self.appearanceLocation = npc.appearanceLocation?.compactMap({ item -> Data? in
try? JSONEncoder().encode(item)
})
self.translations = npc.translations.toDictionary()
}

func toDomain() -> NPC {
func toDomain(appearanceLocation: [AppearanceLocation]?) -> NPC {
return NPC(
name: self.name ?? "",
iconImage: self.iconImage ?? "",
photoImage: self.photoImage ?? "",
gender: Gender(rawValue: self.gender ?? "") ?? .male,
genderAsia: Gender(rawValue: self.gender ?? "") ?? .male,
species: self.species ?? "",
birthday: self.birthday ?? "",
appearanceLocation: appearanceLocation ?? [],
translations: Translations(self.translations ?? [:])
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// AppearanceLocation.swift
// ACNH-wiki
//
// Created by Ari on 11/26/24.
//

import Foundation

struct AppearanceLocation: Codable {
let place: String
let time: Time?
let conditions: String?
let features: [String]?
let schedule: [Schedule]?
}

struct Time: Codable {
let start: String
let end: String
let nextDay: Bool?

var formatted: String {
let nextDay = nextDay == true ? "Next day " : ""
return "\(start) - \(nextDay.localized + end)"
}
}

struct Schedule: Codable {
let day: String
let note: String
}
2 changes: 2 additions & 0 deletions Animal-Crossing-Wiki/Projects/App/Sources/Models/NPC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ struct NPC {
let photoImage: String?
let gender: Gender
let genderAsia: Gender
let species: String
let birthday: String
let appearanceLocation: [AppearanceLocation]?
let translations: Translations
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ struct NPCResponseDTO: Decodable {
let photoImage: String?
let gender: Gender
let genderAsia: Gender
let species: String
let versionAdded: String?
let npcID: String
let internalID: Int
Expand All @@ -22,12 +23,13 @@ struct NPCResponseDTO: Decodable {
let iconFilename: String?
let photoFilename: String?
let uniqueEntryID: String
let appearanceLocation: [AppearanceLocation]?
let translations: Translations

enum CodingKeys: String, CodingKey {
case name, iconImage, photoImage, gender, genderAsia, versionAdded,
birthday, nameColor, bubbleColor, iconFilename, photoFilename,
translations
translations, species, appearanceLocation
case npcID = "npcId"
case internalID = "internalId"
case uniqueEntryID = "uniqueEntryId"
Expand All @@ -42,7 +44,9 @@ extension NPCResponseDTO {
photoImage: photoImage,
gender: gender,
genderAsia: genderAsia,
species: species,
birthday: birthday,
appearanceLocation: appearanceLocation,
translations: translations
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import UIKit
import RxSwift
import SwiftUI

final class NPCDetailViewController: UIViewController {

Expand Down Expand Up @@ -82,10 +83,31 @@ final class NPCDetailViewController: UIViewController {
reactor.state.map { $0.npc }
.take(1)
.observe(on: MainScheduler.instance)
.subscribe(onNext: { [weak self] npc in
.subscribe(onNext: { [weak self] npc in
let detailSection = NPCDetailView(npc)
self?.sectionsScrollView.addSection(SectionView(contentView: detailSection))
self?.navigationItem.title = npc.translations.localizedName()
self?.setUpAppearanceLocation(npc.appearanceLocation ?? [])
}).disposed(by: disposeBag)
}

private func setUpAppearanceLocation(_ appearanceLocation: [AppearanceLocation]) {
guard appearanceLocation.isEmpty == false else {
return
}

appearanceLocation.enumerated().forEach { index, item in
let hosting = UIHostingController(rootView: AppearanceLocationView(item: item))
hosting.view.backgroundColor = .clear
if index == 0 {
sectionsScrollView.addSection(
SectionView(title: "appearance location".localized, iconName: "calendar", contentView: hosting.view)
)
} else {
sectionsScrollView.addSection(
SectionView(contentView: hosting.view)
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ final class NPCViewController: UIViewController {
view.addSubviews(collectionView, emptyView)
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: view.topAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
emptyView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ final class VillagersViewController: UIViewController {
view.addSubviews(collectionView, emptyView)
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: view.topAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
emptyView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// AppearanceLocationView.swift
// ACNH-wiki
//
// Created by Ari on 11/27/24.
//

import SwiftUI

struct AppearanceLocationView: View {
let item: AppearanceLocation

var body: some View {
VStack(spacing: 10) {
infoView(title: "place".localized, description: item.place.localized)
if let time = item.time?.formatted {
infoView(title: "time".localized, description: time)
}
if let conditions = item.conditions {
infoView(title: "conditions".localized, description: conditions.localized)
}
if let features = item.features?.map({ $0.localized }).joined(separator: "\n") {
infoView(title: "features".localized, description: features)
}
}
.background(SwiftUI.Color.clear)
.padding(.horizontal, 20)
.padding(.vertical, 20)
}

@ViewBuilder
func infoView(title: String, description: String) -> some View {
HStack(alignment: .firstTextBaseline, spacing: 0) {
Text(title)
.font(.callout)
.fontWeight(.semibold)
.foregroundStyle(SwiftUI.Color(uiColor: .acText))

Spacer(minLength: 8)

Text(description)
.font(.footnote)
.fontWeight(.regular)
.multilineTextAlignment(.trailing)
.foregroundStyle(SwiftUI.Color(uiColor: .acSecondaryText))

}
.background(SwiftUI.Color.clear)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ extension NPCDetailView {

let items: [(title: String, value: String)] = [
("Gender".localized, npc.gender.rawValue.lowercased().localized.capitalized),
("Birthday".localized, npc.birthday)
("Birthday".localized, npc.birthday),
("Specie".localized, npc.species.lowercased().localized.capitalized)
]

let contentViews = items.map { item -> InfoContentView in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,15 @@ final class ItemsViewController: UIViewController {
navigationController?.navigationBar.sizeToFit()
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if mode == .search {
DispatchQueue.main.async { [weak self] in
self?.searchController.searchBar.becomeFirstResponder()
}
}
}

func bind(to reactor: ItemsReactor, keyword: [Menu: String] = [:]) {
self.reactor = reactor

Expand Down Expand Up @@ -258,7 +267,7 @@ final class ItemsViewController: UIViewController {
view.addSubviews(collectionView, activityIndicator, emptyView)
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: view.topAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
activityIndicator.widthAnchor.constraint(equalTo: view.widthAnchor),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,9 @@ final class ItemsReactor: Reactor {
currentKeywords = keywords
return currentItems()
.map { [weak self] items -> [Item] in
guard let owner = self else { return [] }
guard let owner = self else {
return []
}
return owner.filtered(
items: owner.search(items: items, text: owner.lastSearchKeyword),
keywords: keywords
Expand Down Expand Up @@ -305,7 +307,10 @@ final class ItemsReactor: Reactor {
case .keyword(let title, _):
return Items.shared.itemList
.map { $0.values.flatMap { $0.filter { $0.keyword.contains(title) } } }


case .search:
return Items.shared.itemList
.map { $0.flatMap { $0.value } }
default:
return .empty()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,24 +78,34 @@ final class PreferencesViewController: UIViewController {
}).disposed(by: disposeBag)

settingSection.reputationButtonObservable
.subscribe(with: self, onNext: { owner, _ in
owner.showSelectedItemAlert(
.flatMap { [weak self] _ -> Observable<String> in
guard let owner = self else {
return .empty()
}

return owner.showSelectedItemAlert(
["⭐️", "⭐️⭐️", "⭐️⭐️⭐️", "⭐️⭐️⭐️⭐️", "⭐️⭐️⭐️⭐️⭐️"],
currentItem: owner.currentReputation.value
).map { PreferencesReactor.Action.reputation($0) }
.bind(to: reactor.action)
.disposed(by: owner.disposeBag)
}).disposed(by: disposeBag)
)
}
.map { PreferencesReactor.Action.reputation($0) }
.bind(to: reactor.action)
.disposed(by: disposeBag)

settingSection.startingFruitButtonObservable
.subscribe(with: self, onNext: { owner, _ in
owner.showSelectedItemAlert(
.flatMap { [weak self] _ -> Observable<String> in
guard let owner = self else {
return .empty()
}

return owner.showSelectedItemAlert(
Fruit.allCases.map { $0.rawValue.lowercased().localized },
currentItem: owner.currentFruit.value
).map { PreferencesReactor.Action.fruit(title: $0)}
.bind(to: reactor.action)
.disposed(by: owner.disposeBag)
}).disposed(by: disposeBag)
)
}
.map { PreferencesReactor.Action.fruit(title: $0)}
.bind(to: reactor.action)
.disposed(by: disposeBag)

reactor.state
.compactMap { $0.userInfo }
Expand Down
5 changes: 3 additions & 2 deletions Animal-Crossing-Wiki/Projects/App/Sources/Utility/Items.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ final class Items {

private init() {
setUpUserCollection()
fetchPeople()
fetchAnimals()
fetchCritters()
fetchFurniture()
fetchClothes()
Expand All @@ -65,6 +65,7 @@ final class Items {

self.villagersLike.accept(CoreDataVillagersLikeStorage().fetch())
self.villagersHouse.accept(CoreDataVillagersHouseStorage().fetch())
self.npcLike.accept(CoreDataNPCLikeStorage().fetch())

CoreDataItemsStorage().fetch()
.subscribe(onSuccess: { items in
Expand All @@ -81,7 +82,7 @@ final class Items {
}

// MARK: Fetch Items
private func fetchPeople() {
private func fetchAnimals() {
networkGroup.enter()
let group = DispatchGroup()
fetchItem(VillagersRequest(), group: group) { [weak self] response in
Expand Down
Loading