Adds MarkdownUI dependency and abilities.

This commit is contained in:
2021-03-25 01:22:37 -07:00
parent 9fd4c1f71d
commit a9ad7a7fa8
11 changed files with 415 additions and 7 deletions

View File

@@ -0,0 +1,116 @@
//
// AbilityViewModel.swift
// MonsterCards
//
// Created by Tom Hicks on 3/25/21.
//
import Foundation
public class AbilityViewModel: NSObject, ObservableObject, Identifiable, NSSecureCoding {
public static var supportsSecureCoding = true
public func encode(with coder: NSCoder) {
coder.encode(self.name, forKey: "name")
coder.encode(self.abilityDescription, forKey: "abilityDescription")
}
public required init?(coder: NSCoder) {
self.name = coder.decodeObject(of: NSString.self, forKey: "name")! as String
self.abilityDescription = coder.decodeObject(of: NSString.self, forKey: "abilityDescription")! as String
}
@Published public var name: String
@Published public var abilityDescription: String
public init(_ name: String = "", _ abilityDescription: String = "") {
self.name = name
self.abilityDescription = abilityDescription
}
public var fullText: String {
get {
return String(format: "___%@:___ %@", name, abilityDescription)
}
}
public func renderedText(_ monster: Monster) -> String {
let strSave = monster.strengthModifier + monster.proficiencyBonus + 8
let dexSave = monster.dexterityModifier + monster.proficiencyBonus + 8
let conSave = monster.constitutionModifier + monster.proficiencyBonus + 8
let intSave = monster.intelligenceModifier + monster.proficiencyBonus + 8
let wisSave = monster.wisdomModifier + monster.proficiencyBonus + 8
let chaSave = monster.charismaModifier + monster.proficiencyBonus + 8
let strAttack = monster.strengthModifier + monster.proficiencyBonus
let dexAttack = monster.dexterityModifier + monster.proficiencyBonus
let conAttack = monster.constitutionModifier + monster.proficiencyBonus
let intAttack = monster.intelligenceModifier + monster.proficiencyBonus
let wisAttack = monster.wisdomModifier + monster.proficiencyBonus
let chaAttack = monster.charismaModifier + monster.proficiencyBonus
// TODO: find the other options and implement them [WIS], [WIS STAT], [WIS DMG], [WIS STAT 1d12]
let finalText = fullText
.replacingOccurrences(of: "[STR SAVE]", with: String(strSave))
.replacingOccurrences(of: "[DEX SAVE]", with: String(dexSave))
.replacingOccurrences(of: "[CON SAVE]", with: String(conSave))
.replacingOccurrences(of: "[INT SAVE]", with: String(intSave))
.replacingOccurrences(of: "[WIS SAVE]", with: String(wisSave))
.replacingOccurrences(of: "[CHA SAVE]", with: String(chaSave))
.replacingOccurrences(of: "[STR ATK]", with: String(strAttack))
.replacingOccurrences(of: "[DEX ATK]", with: String(dexAttack))
.replacingOccurrences(of: "[CON ATK]", with: String(conAttack))
.replacingOccurrences(of: "[INT ATK]", with: String(intAttack))
.replacingOccurrences(of: "[WIS ATK]", with: String(wisAttack))
.replacingOccurrences(of: "[CHA ATK]", with: String(chaAttack))
return finalText
}
}
extension AbilityViewModel: Comparable {
public static func < (lhs: AbilityViewModel, rhs: AbilityViewModel) -> Bool {
lhs.name < rhs.name
}
public static func == (lhs: AbilityViewModel, rhs: AbilityViewModel) -> Bool {
lhs.name == rhs.name &&
lhs.abilityDescription == rhs.abilityDescription
}
}
// TODO: figure out how to add this to the set of known transformers so it will work with transformer set to NSSecureUnarchiveFromDataTransformerName
@objc(AbilityViewModelValueTransformer)
public final class AbilityViewModelValueTransformer: 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 `AbilityViewModel` 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: AbilityViewModel.self, from: data as Data)
return language
} catch {
assertionFailure("Failed to transform `Data` to `AbilityViewModel`")
return nil
}
}
}

View File

@@ -60,6 +60,7 @@ class MonsterViewModel: ObservableObject {
@Published var challengeRating: ChallengeRating
@Published var customChallengeRating: String
@Published var customProficiencyBonus: Int64
@Published var abilities: [AbilityViewModel]
init(_ rawMonster: Monster? = nil) {
self.name = ""
@@ -112,6 +113,7 @@ class MonsterViewModel: ObservableObject {
self.challengeRating = .one
self.customChallengeRating = ""
self.customProficiencyBonus = 0
self.abilities = []
if (rawMonster != nil) {
self.copyFromMonster(monster: rawMonster!)
@@ -186,7 +188,12 @@ class MonsterViewModel: ObservableObject {
.sorted()
self.languages = (monster.languages ?? [])
.map {LanguageViewModel($0.name, $0.speaks)}
.sorted()
// These are manually sorted in the UI
self.abilities = (monster.abilities ?? [])
.map {AbilityViewModel($0.name, $0.abilityDescription)}
}
func copyToMonster(monster: Monster) {
@@ -262,5 +269,7 @@ class MonsterViewModel: ObservableObject {
// This is necessary so core data sees the language objects as changed. Without it they won't be persisted.
monster.languages = languages.map {LanguageViewModel($0.name, $0.speaks)}
monster.abilities = abilities.map {AbilityViewModel($0.name, $0.abilityDescription)}
}
}

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17709" systemVersion="20D91" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="YES" userDefinedModelVersionIdentifier="">
<entity name="Monster" representedClassName="Monster" syncable="YES" codeGenerationType="category">
<attribute name="abilities" optional="YES" attributeType="Transformable" valueTransformerName="AbilityViewModelValueTransformer" customClassName="[AbilityViewModel]"/>
<attribute name="alignment" attributeType="String" defaultValueString=""/>
<attribute name="armorType" attributeType="String" defaultValueString=""/>
<attribute name="baseSpeed" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
@@ -67,7 +68,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="884"/>
<element name="Monster" positionX="-63" positionY="-18" width="128" height="899"/>
<element name="Skill" positionX="-63" positionY="135" width="128" height="14"/>
</elements>
</model>

View File

@@ -0,0 +1,64 @@
//
// EditAbilities.swift
// MonsterCards
//
// Created by Tom Hicks on 3/25/21.
//
import SwiftUI
struct EditAbilities: View {
@ObservedObject var viewModel: MonsterViewModel
var path: ReferenceWritableKeyPath<MonsterViewModel, [AbilityViewModel]>
var title: String
var body: some View {
List {
ForEach(viewModel[keyPath: path]) { ability in
NavigationLink(
ability.name,
destination: EditAbility(viewModel: ability))
}
.onDelete(perform: { indexSet in
for index in indexSet {
viewModel[keyPath: path].remove(at: index)
}
})
.onMove(perform: { indices, newOffset in
viewModel[keyPath: path].move(fromOffsets: indices, toOffset: newOffset)
})
}
.toolbar(content: {
ToolbarItemGroup(placement: .navigationBarTrailing) {
EditButton()
Button(
action: {
let newAbility = AbilityViewModel()
viewModel[keyPath: path].append(newAbility)
viewModel[keyPath: path] = viewModel[keyPath: path].sorted()
},
label: {
Image(systemName: "plus")
}
)
}
})
.onAppear(perform: {
viewModel[keyPath: path] = viewModel[keyPath: path].sorted()
})
.navigationTitle(title)
}
}
struct EditAbilities_Previews: PreviewProvider {
static var previews: some View {
let viewModel = MonsterViewModel()
EditAbilities(
viewModel: viewModel,
path: \.abilities,
title: "Abilities")
}
}

View File

@@ -0,0 +1,29 @@
//
// EditAbility.swift
// MonsterCards
//
// Created by Tom Hicks on 3/25/21.
//
import SwiftUI
struct EditAbility: View {
@ObservedObject var viewModel: AbilityViewModel
var body: some View {
VStack {
MCTextField(
label: "Name",
value: $viewModel.name)
TextEditor(text: $viewModel.abilityDescription)
}
}
}
struct EditAbility_Previews: PreviewProvider {
static var previews: some View {
let viewModel = AbilityViewModel()
EditAbility(viewModel: viewModel)
}
}

View File

@@ -73,6 +73,9 @@ struct EditMonster: View {
NavigationLink(
"Challenge Rating",
destination: EditChallengeRating(viewModel: monsterViewModel))
NavigationLink(
"Abilities", destination: EditAbilities(viewModel: monsterViewModel, path: \.abilities, title: "Abilities"))
}
}

View File

@@ -33,8 +33,8 @@ struct EditStrings: View {
.toolbar(content: {
Button(
action: {
let newDamageType = StringViewModel()
viewModel[keyPath: path].append(newDamageType)
let newString = StringViewModel()
viewModel[keyPath: path].append(newString)
viewModel[keyPath: path] = viewModel[keyPath: path].sorted()
},
label: {
@@ -44,13 +44,17 @@ struct EditStrings: View {
})
.onAppear(perform: {
viewModel[keyPath: path] = viewModel[keyPath: path].sorted()
}).navigationTitle(title)
})
.navigationTitle(title)
}
}
struct EditStrings_Previews: PreviewProvider {
static var previews: some View {
let viewModel = MonsterViewModel()
EditStrings(viewModel: viewModel, path: \.damageImmunities, title: "Damage Types")
EditStrings(
viewModel: viewModel,
path: \.damageImmunities,
title: "Damage Types")
}
}

View File

@@ -6,6 +6,7 @@
//
import SwiftUI
import MarkdownUI
struct LabeledField<Content: View>: View {
@Environment(\.horizontalSizeClass) var sizeClass
@@ -209,6 +210,7 @@ struct MonsterDetail: View {
VStack (alignment: .leading) {
let monsterLanguagesDescription = monster.languagesDescription
let monsterChallengeRatingDescription = monster.challengeRatingDescription
let monsterAbilities: [AbilityViewModel] = monster.abilities ?? []
BasicInfoView(monster: monster)
@@ -243,7 +245,15 @@ struct MonsterDetail: View {
}
// Abilities
if (monsterAbilities.count > 0) {
ForEach(monsterAbilities) { ability in
VStack {
Markdown(Document(ability.renderedText(monster)/*.fullText*/))
Divider()
}
}
}
// Actions
// Legendary Actions