Adds languages to the editor.
This commit is contained in:
79
iOS/MonsterCards/Models/LanguageViewModel.swift
Normal file
79
iOS/MonsterCards/Models/LanguageViewModel.swift
Normal file
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// LanguageViewModel.swift
|
||||
// MonsterCards
|
||||
//
|
||||
// Created by Tom Hicks on 3/24/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// TODO: split this into separate Model and ViewModel classes later.
|
||||
public class LanguageViewModel : NSObject, ObservableObject, Comparable, Identifiable, NSSecureCoding {
|
||||
public static var supportsSecureCoding = true
|
||||
|
||||
public func encode(with coder: NSCoder) {
|
||||
coder.encode(self.name, forKey: "name")
|
||||
coder.encode(self.speaks, forKey: "speaks")
|
||||
}
|
||||
|
||||
public required init?(coder: NSCoder) {
|
||||
self.name = coder.decodeObject(of: NSString.self, forKey: "name")! as String
|
||||
self.speaks = coder.decodeBool(forKey: "speaks")
|
||||
}
|
||||
|
||||
public static func < (lhs: LanguageViewModel, rhs: LanguageViewModel) -> Bool {
|
||||
lhs.name < rhs.name
|
||||
}
|
||||
|
||||
public static func == (lhs: LanguageViewModel, rhs: LanguageViewModel) -> Bool {
|
||||
lhs.name == rhs.name &&
|
||||
lhs.speaks == rhs.speaks
|
||||
}
|
||||
|
||||
@Published var name: String
|
||||
@Published var speaks: Bool
|
||||
|
||||
init(
|
||||
_ name: String = "",
|
||||
_ speaks: Bool = true
|
||||
) {
|
||||
self.name = name
|
||||
self.speaks = speaks
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: figure out how to add this to the set of known transformers so it will work with transformer set to NSSecureUnarchiveFromDataTransformerName
|
||||
@objc(LanguageViewModelValueTransformer)
|
||||
public final class LanguageViewModelValueTransformer: ValueTransformer {
|
||||
override public class func transformedValueClass() -> AnyClass {
|
||||
return NSArray.self
|
||||
}
|
||||
|
||||
override public class func allowsReverseTransformation() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override public func transformedValue(_ value: Any?) -> Any? {
|
||||
guard let language = value as? NSArray else { return nil }
|
||||
|
||||
do {
|
||||
let data = try NSKeyedArchiver.archivedData(withRootObject: language, requiringSecureCoding: true)
|
||||
return data
|
||||
} catch {
|
||||
assertionFailure("Failed to transform `LanguageViewModel` to `Data`")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
override public func reverseTransformedValue(_ value: Any?) -> Any? {
|
||||
guard let data = value as? NSData else { return nil }
|
||||
|
||||
do {
|
||||
let language = try NSKeyedUnarchiver.unarchivedArrayOfObjects(ofClass: LanguageViewModel.self, from: data as Data)
|
||||
return language
|
||||
} catch {
|
||||
assertionFailure("Failed to transform `Data` to `LanguageViewModel`")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -698,8 +698,51 @@ public class Monster: NSManagedObject {
|
||||
|
||||
var languagesDescription: String {
|
||||
get {
|
||||
let sortedLanguages = self.languagesArray.sorted()
|
||||
return StringHelper.oxfordJoin(sortedLanguages, ", ", ", and ", " and ")
|
||||
let spokenLanguages = (self.languages ?? [])
|
||||
.filter({ $0.speaks })
|
||||
.map({$0.name})
|
||||
.sorted()
|
||||
let understoodLanguages = (self.languages ?? [])
|
||||
.filter({ !$0.speaks })
|
||||
.map({$0.name})
|
||||
.sorted()
|
||||
|
||||
let understandsButText = understandsBut?.isEmpty ?? false
|
||||
? ""
|
||||
: String(format: " but %@", understandsBut!)
|
||||
|
||||
let telepathyText = telepathy > 0
|
||||
? String(format: ", telepathy %d ft.", telepathy)
|
||||
: ""
|
||||
|
||||
if (spokenLanguages.count > 0) {
|
||||
if (understoodLanguages.count > 0) {
|
||||
return String(
|
||||
format:"%@ and understands %@%@%@",
|
||||
StringHelper.oxfordJoin(spokenLanguages),
|
||||
StringHelper.oxfordJoin(understoodLanguages),
|
||||
understandsButText,
|
||||
telepathyText)
|
||||
} else {
|
||||
return String(
|
||||
format: "%@%@%@",
|
||||
StringHelper.oxfordJoin(spokenLanguages),
|
||||
understandsButText,
|
||||
telepathyText)
|
||||
}
|
||||
} else {
|
||||
if (understoodLanguages.count > 0) {
|
||||
return String(
|
||||
format: "understands %@%@%@",
|
||||
StringHelper.oxfordJoin(understoodLanguages),
|
||||
understandsButText,
|
||||
telepathyText)
|
||||
} else if (telepathy > 0){
|
||||
return String(format: "telepathy %d ft.", telepathy)
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,9 @@ class MonsterViewModel: ObservableObject {
|
||||
@Published var damageVulnerabilities: [StringViewModel]
|
||||
@Published var conditionImmunities: [StringViewModel]
|
||||
@Published var senses: [StringViewModel]
|
||||
@Published var languages: [LanguageViewModel]
|
||||
@Published var telepathy: Int64
|
||||
@Published var understandsBut: String
|
||||
|
||||
init(_ rawMonster: Monster? = nil) {
|
||||
self.name = ""
|
||||
@@ -100,6 +103,9 @@ class MonsterViewModel: ObservableObject {
|
||||
self.damageVulnerabilities = []
|
||||
self.conditionImmunities = []
|
||||
self.senses = []
|
||||
self.languages = []
|
||||
self.telepathy = 0
|
||||
self.understandsBut = ""
|
||||
|
||||
if (rawMonster != nil) {
|
||||
self.copyFromMonster(monster: rawMonster!)
|
||||
@@ -145,6 +151,8 @@ class MonsterViewModel: ObservableObject {
|
||||
self.charismaScore = monster.charismaScore
|
||||
self.charismaSavingThrowAdvantage = monster.charismaSavingThrowAdvantageEnum
|
||||
self.charismaSavingThrowProficiency = monster.charismaSavingThrowProficiencyEnum
|
||||
self.telepathy = monster.telepathy
|
||||
self.understandsBut = monster.understandsBut ?? ""
|
||||
self.skills = (monster.skills?.allObjects.map {SkillViewModel(($0 as! Skill))})!.sorted()
|
||||
|
||||
self.damageImmunities = (monster.damageImmunities ?? [])
|
||||
@@ -166,6 +174,9 @@ class MonsterViewModel: ObservableObject {
|
||||
self.senses = (monster.senses ?? [])
|
||||
.map {StringViewModel($0)}
|
||||
.sorted()
|
||||
|
||||
self.languages = (monster.languages ?? [])
|
||||
.sorted()
|
||||
}
|
||||
|
||||
func copyToMonster(monster: Monster) {
|
||||
@@ -207,6 +218,8 @@ class MonsterViewModel: ObservableObject {
|
||||
monster.charismaScore = charismaScore
|
||||
monster.charismaSavingThrowAdvantageEnum = charismaSavingThrowAdvantage
|
||||
monster.charismaSavingThrowProficiencyEnum = charismaSavingThrowProficiency
|
||||
monster.telepathy = telepathy
|
||||
monster.understandsBut = understandsBut
|
||||
|
||||
// Remove missing skills from raw monster
|
||||
monster.skills?.forEach {s in
|
||||
@@ -233,5 +246,6 @@ class MonsterViewModel: ObservableObject {
|
||||
monster.damageResistances = damageResistances.map {$0.name}
|
||||
monster.damageVulnerabilities = damageVulnerabilities.map {$0.name}
|
||||
monster.senses = senses.map {$0.name}
|
||||
monster.languages = languages.map {LanguageViewModel($0.name, $0.speaks)}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
<attribute name="intelligenceSavingThrowProficiency" attributeType="String" defaultValueString="none"/>
|
||||
<attribute name="intelligenceScore" attributeType="Integer 64" defaultValueString="10" usesScalarValueType="YES"/>
|
||||
<attribute name="isBlind" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="languages" optional="YES" attributeType="Transformable" valueTransformerName="LanguageViewModelValueTransformer" customClassName="[LanguageViewModel]"/>
|
||||
<attribute name="name" attributeType="String" defaultValueString="Unnamed Monster"/>
|
||||
<attribute name="naturalArmorBonus" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="otherArmorDescription" attributeType="String" defaultValueString=""/>
|
||||
@@ -47,6 +48,7 @@
|
||||
<attribute name="strengthScore" attributeType="Integer 64" defaultValueString="10" usesScalarValueType="YES"/>
|
||||
<attribute name="subtype" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="swimSpeed" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="telepathy" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="tremorsenseDistance" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="truesightDistance" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="type" attributeType="String" defaultValueString=""/>
|
||||
@@ -64,7 +66,7 @@
|
||||
<relationship name="monster" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Monster" inverseName="skills" inverseEntity="Monster"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Monster" positionX="-63" positionY="-18" width="128" height="839"/>
|
||||
<element name="Monster" positionX="-63" positionY="-18" width="128" height="869"/>
|
||||
<element name="Skill" positionX="-63" positionY="135" width="128" height="14"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -38,19 +38,6 @@ struct PersistenceController {
|
||||
}
|
||||
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
|
||||
if let error = error as NSError? {
|
||||
|
||||
// NSPersistentStoreCoordinator.destroyPersistentStore(storeDes)
|
||||
// Replace this implementation with code to handle the error appropriately.
|
||||
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
|
||||
|
||||
/*
|
||||
Typical reasons for an error here include:
|
||||
* The parent directory does not exist, cannot be created, or disallows writing.
|
||||
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
|
||||
* The device is out of space.
|
||||
* The store could not be migrated to the current model version.
|
||||
Check the error message to determine what the actual problem was.
|
||||
*/
|
||||
fatalError("Unresolved error \(error), \(error.userInfo)")
|
||||
}
|
||||
})
|
||||
|
||||
28
iOS/MonsterCards/Views/EditLanguage.swift
Normal file
28
iOS/MonsterCards/Views/EditLanguage.swift
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// EditLanguage.swift
|
||||
// MonsterCards
|
||||
//
|
||||
// Created by Tom Hicks on 3/24/21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct EditLanguage: View {
|
||||
@ObservedObject var viewModel: LanguageViewModel
|
||||
|
||||
var body: some View {
|
||||
MCTextField(
|
||||
label: "Name",
|
||||
value: $viewModel.name)
|
||||
.autocapitalization(.none)
|
||||
|
||||
Toggle("Speaks", isOn: $viewModel.speaks)
|
||||
}
|
||||
}
|
||||
|
||||
struct EditLanguage_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let viewModel = LanguageViewModel()
|
||||
EditLanguage(viewModel: viewModel)
|
||||
}
|
||||
}
|
||||
48
iOS/MonsterCards/Views/EditLanguages.swift
Normal file
48
iOS/MonsterCards/Views/EditLanguages.swift
Normal file
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// EditLanguages.swift
|
||||
// MonsterCards
|
||||
//
|
||||
// Created by Tom Hicks on 3/24/21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct EditLanguages: View {
|
||||
@ObservedObject var viewModel: MonsterViewModel
|
||||
|
||||
var body: some View {
|
||||
let sortedLanguages = viewModel.languages.sorted()
|
||||
List {
|
||||
MCTextField(
|
||||
label: "Understands But",
|
||||
value: $viewModel.understandsBut)
|
||||
MCStepperField(label: "Telepathy", prefix: "", step: 5, suffix: " ft.", value: $viewModel.telepathy)
|
||||
ForEach(sortedLanguages/*viewModel.languages*/) { language in
|
||||
NavigationLink(language.name, destination: EditLanguage(viewModel: language))
|
||||
}
|
||||
|
||||
}
|
||||
.toolbar(content: {
|
||||
Button(
|
||||
action: {
|
||||
let newLanguage = LanguageViewModel("English")
|
||||
viewModel.languages.append(newLanguage)
|
||||
viewModel.languages = viewModel.languages.sorted()
|
||||
},
|
||||
label: {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
)
|
||||
})
|
||||
.onAppear(perform: {
|
||||
viewModel.languages = viewModel.languages.sorted()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct EditLanguages_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let viewModel = MonsterViewModel()
|
||||
EditLanguages(viewModel: viewModel)
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,10 @@ struct EditMonster: View {
|
||||
NavigationLink(
|
||||
"Senses",
|
||||
destination: EditStrings(viewModel: monsterViewModel, path: \.senses, title: "Senses"))
|
||||
|
||||
NavigationLink(
|
||||
"Languages",
|
||||
destination: EditLanguages(viewModel: monsterViewModel))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -302,6 +302,11 @@ struct MonsterDetail_Previews: PreviewProvider {
|
||||
monster.wisdomSavingThrowProficiencyEnum = ProficiencyType.proficient
|
||||
monster.charismaSavingThrowAdvantageEnum = AdvantageType.disadvantage
|
||||
monster.charismaSavingThrowProficiencyEnum = ProficiencyType.none
|
||||
monster.telepathy = 1
|
||||
monster.languages = [
|
||||
LanguageViewModel("English", true),
|
||||
LanguageViewModel("French", false)
|
||||
]
|
||||
|
||||
return Group {
|
||||
MonsterDetail(monster: monster)
|
||||
|
||||
Reference in New Issue
Block a user