Series Picker now works together with series import/export

This commit is contained in:
2021-05-20 11:46:50 +02:00
parent 59fc67b5f0
commit 1d51519ad0
11 changed files with 158 additions and 94 deletions

View File

@@ -46,6 +46,7 @@
B9EC09822383F94B004BC9AB /* LibraryExport.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EC09812383F94B004BC9AB /* LibraryExport.swift */; };
B9EC098523854C24004BC9AB /* QGrid in Frameworks */ = {isa = PBXBuildFile; productRef = B9EC098423854C24004BC9AB /* QGrid */; };
B9EC0987238555BF004BC9AB /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EC0986238555BF004BC9AB /* SettingsView.swift */; };
B9ED3DD7265534C000FD2D46 /* test_date_utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9ED3DD6265534C000FD2D46 /* test_date_utils.swift */; };
B9F44ABA22F312E600FC6B29 /* ConsoleLibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9F44AB922F312E600FC6B29 /* ConsoleLibraryView.swift */; };
B9F44ABE22F31DEF00FC6B29 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9F44ABD22F31DEF00FC6B29 /* SceneDelegate.swift */; };
/* End PBXBuildFile section */
@@ -118,6 +119,7 @@
B9E2A07C233B6E4F00EAEB14 /* ConsoleAllView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsoleAllView.swift; sourceTree = "<group>"; };
B9EC09812383F94B004BC9AB /* LibraryExport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryExport.swift; sourceTree = "<group>"; };
B9EC0986238555BF004BC9AB /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
B9ED3DD6265534C000FD2D46 /* test_date_utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = test_date_utils.swift; sourceTree = "<group>"; };
B9F44AB922F312E600FC6B29 /* ConsoleLibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleLibraryView.swift; sourceTree = "<group>"; };
B9F44ABD22F31DEF00FC6B29 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -245,6 +247,7 @@
B98CBBE92652B75F00B1B7AC /* ZockerhoehleTests */ = {
isa = PBXGroup;
children = (
B9ED3DD6265534C000FD2D46 /* test_date_utils.swift */,
B98CBBEA2652B75F00B1B7AC /* ZockerhoehleTests.swift */,
B98CBBEC2652B75F00B1B7AC /* Info.plist */,
);
@@ -426,6 +429,7 @@
buildActionMask = 2147483647;
files = (
B98CBBEB2652B75F00B1B7AC /* ZockerhoehleTests.swift in Sources */,
B9ED3DD7265534C000FD2D46 /* test_date_utils.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -22,16 +22,7 @@ public class Console: NSManagedObject, Identifiable {
Game.compareByCreationDate(gameA: $0, gameB: $1)
}) else { return false }
guard let gameACreated = newestGameConsoleA.createdAt else { return true }
guard let gameBCreated = newestGameConsoleB.createdAt else { return false }
return gameACreated > gameBCreated
}
public var id : NSManagedObjectID {
get {
return self.objectID
}
return newestGameConsoleA.createdAt > newestGameConsoleB.createdAt
}
// Defining default values during creation

View File

@@ -26,25 +26,14 @@ public class Game: NSManagedObject, Identifiable {
}
public static func compareByCreationDate(gameA : Game, gameB : Game) -> Bool {
guard let gameACreated = gameA.createdAt else { return true }
guard let gameBCreated = gameB.createdAt else { return false }
return gameACreated < gameBCreated
}
public var id : NSManagedObjectID {
get {
return self.objectID
}
return gameA.createdAt < gameB.createdAt
}
@objc
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) {
super.init(entity: entity, insertInto: context)
if self.createdAt == .none {
self.createdAt = Date()
}
self.uuid = UUID()
print("Set UUID to \(self.uuid)")
}
@@ -62,8 +51,9 @@ extension Game : Encodable {
case isFinished
case inWishlist
case console
case gameSeries
case series
case cover_icloud_path
case pickupDescription
}
public func encode(to encoder: Encoder) throws {
@@ -74,7 +64,8 @@ extension Game : Encodable {
try container.encode(notes ?? "", forKey: .notes)
try container.encode(isDigital, forKey: .isDigital)
try container.encode(lentTo ?? "", forKey: .lentTo)
try container.encode(createdAt?.formattedInTimeZone(), forKey: .createdAt)
try container.encode(createdAt.formattedInTimeZone(), forKey: .createdAt)
try container.encode(pickupDescription ?? "", forKey: .pickupDescription)
try container.encode(publisher ?? "", forKey: .publisher)
try container.encode(isFinished, forKey: .isFinished)
try container.encode(inWishlist, forKey: .inWishlist)
@@ -83,6 +74,6 @@ extension Game : Encodable {
let consoleUUID : String = console?.uuid.uuidString ?? ""
let gameSeriesUUID : String = series?.uuid.uuidString ?? ""
try container.encode(consoleUUID, forKey: .console)
try container.encode(gameSeriesUUID, forKey: .gameSeries)
try container.encode(gameSeriesUUID, forKey: .series)
}
}

View File

@@ -18,7 +18,8 @@ extension Game {
}
@NSManaged public var circumstances: String?
@NSManaged public var createdAt: Date?
@NSManaged public var createdAt: Date
@NSManaged public var pickupDescription : String?
@NSManaged public var inWishlist: Bool
@NSManaged public var isDigital: Bool
@NSManaged public var isFinished: Bool

View File

@@ -13,11 +13,6 @@ import UIKit
@objc(GameSeries)
public class GameSeries: NSManagedObject, Identifiable {
public var id : String {
return objectID.uriRepresentation().absoluteString
}
public override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) {
super.init(entity: entity, insertInto: context)
@@ -44,7 +39,7 @@ extension GameSeries : Encodable {
var gamesList : [String] = []
for game in games {
if let game = game as? Game {
gamesList.append(game.objectID.uriRepresentation().absoluteString)
gamesList.append(game.uuid.uuidString)
}
}
try container.encode(gamesList, forKey: .games)

View File

@@ -9,6 +9,7 @@
import Foundation
extension Date {
static let zockerhoehle_date_format = "yyyy-MM-dd'T'HH:mm:ssZ"
func formattedInTimeZone(timezone : TimeZone = .current) -> String {
// 1) Create a DateFormatter() object.
let format = DateFormatter()
@@ -17,11 +18,19 @@ extension Date {
format.timeZone = timezone
// 3) Set the format of the altered date.
format.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
format.dateFormat = Date.zockerhoehle_date_format
// 4) Set the current date, altered by timezone.
let dateString = format.string(from: self)
return dateString
}
static func from(string : String, timezone : TimeZone = .current) -> Date? {
let format = DateFormatter()
format.timeZone = timezone
format.dateFormat = Date.zockerhoehle_date_format
return format.date(from: string)
}
}

View File

@@ -79,6 +79,14 @@ class LibraryImport {
cdGame.isFinished = game.isFinished
cdGame.lentTo = game.lentTo
cdGame.cover_icloud_path = game.cover_icloud_path
cdGame.pickupDescription = game.pickupDescription
if let date = Date.from(string: game.createdAt) {
cdGame.createdAt = date
}else{
print("Could not decode date '\(game.createdAt)' for game '\(cdGame.name)'")
}
//TODO: cdGame.createdAt = game.createdAt.
cdConsole.addToGames(cdGame)
@@ -190,7 +198,8 @@ struct BHLGame : Decodable {
let inWishlist : Bool
let isFinished : Bool
let notes : String?
let createdAt : String?
let createdAt : String
let pickupDescription : String?
let publisher : String?
let console : UUID?
let series : UUID?
@@ -205,6 +214,7 @@ struct BHLGame : Decodable {
case isFinished
case notes
case createdAt
case pickupDescription
case publisher
case console
case series
@@ -220,12 +230,16 @@ struct BHLGame : Decodable {
inWishlist = try container.decode(Bool.self, forKey: .inWishlist)
isFinished = try container.decode(Bool.self, forKey: .isFinished)
notes = try container.decode(String?.self, forKey: .notes)
createdAt = try container.decode(String?.self, forKey: .notes)
createdAt = try container.decode(String.self, forKey: .createdAt)
pickupDescription = try container.decode(String?.self, forKey: .pickupDescription)
publisher = try container.decode(String?.self, forKey: .publisher)
cover_icloud_path = try container.decode(String?.self, forKey: .cover_icloud_path)
console = try container.decode(UUID?.self, forKey: .console)
series = try container.decode(UUID?.self, forKey: .console)
do {
series = try container.decode(UUID?.self, forKey: .series)
}catch {
series = .none
}
}
}
@@ -292,7 +306,6 @@ struct BHLConsole : Decodable {
shortName = try container.decode(String?.self, forKey: .shortName)
accessories = try container.decode([UUID].self, forKey: .accessories)
releaseDate = try container.decode(Date.self, forKey: .releaseDate)
print("Decode releaseDate: \(releaseDate)")
games = try container.decode([UUID].self, forKey: .games)
}
}

View File

@@ -8,6 +8,38 @@
import SwiftUI
struct GameSeriesPicker: View {
@ObservedObject var game : Game
@State private var selectedGameSeries = ""
@FetchRequest(entity: GameSeries.entity(), sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)])
var gameSeries: FetchedResults<GameSeries>
var body: some View {
Picker("Spieleserie", selection: $selectedGameSeries) {
Text("Keine").tag("")
ForEach(gameSeries) { game_series in
Text(game_series.name ?? "n/a").tag(game_series.uuid.uuidString)
}
}.onChange(of: selectedGameSeries) { newValue in
if let game_series = gameSeries.first(where: {$0.uuid.uuidString == newValue}) {
if game.series != game_series {
game.series = game_series
}
}
}
}
init(game : Game) {
self.game = game
if let game_series = game.series {
_selectedGameSeries = State(initialValue: game_series.uuid.uuidString)
}
}
}
struct GameDetailView : View {
@ObservedObject var game : Game
@@ -15,7 +47,6 @@ struct GameDetailView : View {
@State var hasFinishedDate : Bool = false
@State var playthroughDate : Date = Date()
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
@State var isImportingCover : Bool = false
@@ -23,48 +54,8 @@ struct GameDetailView : View {
let defaultImage = UIImage()
var gameSeriesPicker : some View {
Text("TODO GameSeries Picker")
/*Picker(selection: $game.gameSeries, label:
Text("Spieleserie")
, content: {
Text("Keine").tag("")
ForEach(gameSeriesStore.gameSeries) { gameSeries in
Text("\(gameSeries.name)").tag(gameSeries.id)
}
})*/
}
var body: some View {
Form {
Section {
TextField("Videogame name", text: $game.name ?? "")
//Gray color should indicate immutable data
Text("\(game.console?.name ?? "No console")").foregroundColor(.gray)
Toggle(isOn: $game.isDigital, label: {
Text("Nur Digital")
})
Toggle(isOn: $game.inWishlist, label: {
Text("In Wunschliste")
})
Toggle(isOn: $isLent, label: {
Text("Verliehen?")
}).onReceive(game.objectWillChange) { _ in
self.isLent = self.game.lentTo != "";
}.onAppear() {
self.isLent = self.game.lentTo != "";
}
if isLent {
TextField("Verliehen an", text: $game.lentTo ?? "")
}
gameSeriesPicker
var GameIsFinished : some View {
Group {
Toggle(isOn: $game.isFinished , label: {
Text("Durchgezockt")
})
@@ -82,6 +73,50 @@ struct GameDetailView : View {
displayedComponents: [.date])
}
}
}
var body: some View {
Form {
Section {
TextField("Videogame name", text: $game.name ?? "")
//Gray color should indicate immutable data
Text("\(game.console?.name ?? "No console")").foregroundColor(.gray)
Toggle(isOn: $game.isDigital, label: {
Text("Nur Digital")
})
DatePicker("In Sammlung seit",
selection: $game.createdAt,
in: ...Date(),
displayedComponents: [.date])
HStack {
Text("Anlass")
TextField("Anlass", text: $game.pickupDescription ?? "")
}
Toggle(isOn: $game.inWishlist, label: {
Text("In Wunschliste")
})
Toggle(isOn: $isLent, label: {
Text("Verliehen?")
}).onReceive(game.objectWillChange) { _ in
self.isLent = self.game.lentTo != "";
}.onAppear() {
self.isLent = self.game.lentTo != "";
}
if isLent {
TextField("Verliehen an", text: $game.lentTo ?? "")
}
GameSeriesPicker(game: game)
GameIsFinished
}
Section {
VStack{
@@ -89,9 +124,13 @@ struct GameDetailView : View {
self.isImportingCover = true
}
Group {
Image(uiImage: ICloudManager.imageFrom(path: game.cover_icloud_path) ?? defaultImage)
.resizable()
.frame(width:100, height: 100)
.scaledToFit()
}.frame(width:100, height: 100)
}
}

View File

@@ -87,8 +87,6 @@ struct Overview: View {
.cornerRadius(5)
}
.frame(width: 100, height: 100)
//.background(Color(UIColor.lightGray))
//.cornerRadius(5)
Text("\(console.name!)")
.font(.caption)
@@ -117,17 +115,12 @@ struct Overview: View {
NavigationLink(destination: GameDetailView(game: game)) {
VStack(alignment: .leading) {
Group {
/*game.cover?.image.map {
Image(uiImage: $0)
Image(uiImage: ICloudManager.imageFrom(path: game.cover_icloud_path) ?? defaultImage)
.resizable()
.padding(10)
.aspectRatio(contentMode: .fit)
.cornerRadius(5)
}*/
Text("\("Fetch Limit")")
}
.frame(width: 100, height: 100)
.background(Color(UIColor.lightGray))
.cornerRadius(5)
Text("\(game.name!)")
.font(.caption)
@@ -155,17 +148,12 @@ struct Overview: View {
NavigationLink(destination: GameSeriesLibraryView(gameSeries: game_series)) {
VStack(alignment: .leading) {
Group {
/*game.cover?.image.map {
Image(uiImage: $0)
Image(uiImage: ICloudManager.imageFrom(path: game_series.cover_icloud_path) ?? defaultImage)
.resizable()
.padding(10)
.scaledToFit()
.cornerRadius(5)
}*/
Text("S")
}
.frame(width: 100, height: 100)
.background(Color(UIColor.lightGray))
.cornerRadius(5)
.frame(width: 150, height: 150)
Text("\(game_series.name ?? "n/a")")
.font(.caption)

View File

@@ -33,6 +33,7 @@
<attribute name="lentTo" optional="YES" attributeType="String"/>
<attribute name="name" attributeType="String" defaultValueString=""/>
<attribute name="notes" optional="YES" attributeType="String"/>
<attribute name="pickupDescription" optional="YES" attributeType="String"/>
<attribute name="publisher" optional="YES" attributeType="String"/>
<attribute name="uuid" optional="YES" attributeType="UUID" defaultValueString="00000000-0000-0000-0000-000000000000" usesScalarValueType="NO"/>
<relationship name="console" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Console" inverseName="games" inverseEntity="Console"/>
@@ -57,7 +58,7 @@
<element name="Accessory" positionX="-265.9140625" positionY="29.15625" width="128" height="149"/>
<element name="Console" positionX="-535.7890625" positionY="56.03515625" width="128" height="164"/>
<element name="Cover" positionX="-66.69921875" positionY="223.48046875" width="128" height="44"/>
<element name="Game" positionX="-288.828125" positionY="276.7421875" width="128" height="230"/>
<element name="Game" positionX="-288.828125" positionY="276.7421875" width="128" height="245"/>
<element name="GameSeries" positionX="-686.828125" positionY="359.20703125" width="128" height="89"/>
<element name="GameSeriesCover" positionX="-477" positionY="180" width="128" height="44"/>
<element name="Logo" positionX="-66.7109375" positionY="110.9765625" width="128" height="44"/>

View File

@@ -0,0 +1,32 @@
//
// test_date_utils.swift
// ZockerhoehleTests
//
// Created by Julian-Steffen Müller on 19.05.21.
// Copyright © 2021 Julian-Steffen Müller. All rights reserved.
//
import XCTest
@testable import Zockerhoehle
class test_date_utils: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func test_roundtrip_date_string() throws {
let correct_data_str = "2021-05-19T13:42:05+0200"
let date = Date.from(string: correct_data_str)
let date_str = date?.formattedInTimeZone()
XCTAssertEqual(correct_data_str, date_str)
}
}