Skip to content

Commit

Permalink
[동물] NPC에 종족과 출현장소를 추가 (#83)
Browse files Browse the repository at this point in the history
* feat: NPC에 종족과 출현장소를 추가

* feat: NPC 상세 정보에 종족과 출현 장소를 추가

* fix: 전체 검색에서 사용자가 가지고있는 아이템을 표시하지 않는 현상 개선

* feat: 전체 검색 화면에서 검색창을 자동으로 활성화하도록 개선

* refactor: 이중으로 dispose하고 있던 부분을 flatMap으로 개선
  • Loading branch information
leeari95 authored Nov 30, 2024
1 parent 2eab91f commit 6a2c670
Show file tree
Hide file tree
Showing 17 changed files with 611 additions and 76 deletions.
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

0 comments on commit 6a2c670

Please sign in to comment.