diff --git a/Zockerhoehle.xcodeproj/project.pbxproj b/Zockerhoehle.xcodeproj/project.pbxproj index d1743ac..1df4a31 100644 --- a/Zockerhoehle.xcodeproj/project.pbxproj +++ b/Zockerhoehle.xcodeproj/project.pbxproj @@ -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 = ""; }; B9EC09812383F94B004BC9AB /* LibraryExport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryExport.swift; sourceTree = ""; }; B9EC0986238555BF004BC9AB /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + B9ED3DD6265534C000FD2D46 /* test_date_utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = test_date_utils.swift; sourceTree = ""; }; B9F44AB922F312E600FC6B29 /* ConsoleLibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleLibraryView.swift; sourceTree = ""; }; B9F44ABD22F31DEF00FC6B29 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; /* 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; }; diff --git a/Zockerhoehle/CDModel/Console+CoreDataClass.swift b/Zockerhoehle/CDModel/Console+CoreDataClass.swift index ac32941..866cc77 100644 --- a/Zockerhoehle/CDModel/Console+CoreDataClass.swift +++ b/Zockerhoehle/CDModel/Console+CoreDataClass.swift @@ -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 diff --git a/Zockerhoehle/CDModel/Game+CoreDataClass.swift b/Zockerhoehle/CDModel/Game+CoreDataClass.swift index fa98b6a..f97e08c 100644 --- a/Zockerhoehle/CDModel/Game+CoreDataClass.swift +++ b/Zockerhoehle/CDModel/Game+CoreDataClass.swift @@ -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.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) } } diff --git a/Zockerhoehle/CDModel/Game+CoreDataProperties.swift b/Zockerhoehle/CDModel/Game+CoreDataProperties.swift index 597a1eb..d17528a 100644 --- a/Zockerhoehle/CDModel/Game+CoreDataProperties.swift +++ b/Zockerhoehle/CDModel/Game+CoreDataProperties.swift @@ -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 diff --git a/Zockerhoehle/CDModel/GameSeries+CoreDataClass.swift b/Zockerhoehle/CDModel/GameSeries+CoreDataClass.swift index cb705d8..f15aa78 100644 --- a/Zockerhoehle/CDModel/GameSeries+CoreDataClass.swift +++ b/Zockerhoehle/CDModel/GameSeries+CoreDataClass.swift @@ -12,12 +12,7 @@ import CoreData import UIKit @objc(GameSeries) -public class GameSeries: NSManagedObject, Identifiable { - - public var id : String { - return objectID.uriRepresentation().absoluteString - } - +public class GameSeries: NSManagedObject, Identifiable { 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) diff --git a/Zockerhoehle/Utils/DateConversion.swift b/Zockerhoehle/Utils/DateConversion.swift index 4592852..74ad928 100644 --- a/Zockerhoehle/Utils/DateConversion.swift +++ b/Zockerhoehle/Utils/DateConversion.swift @@ -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) + } } diff --git a/Zockerhoehle/Utils/LibraryImport.swift b/Zockerhoehle/Utils/LibraryImport.swift index 377e263..259012b 100644 --- a/Zockerhoehle/Utils/LibraryImport.swift +++ b/Zockerhoehle/Utils/LibraryImport.swift @@ -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) @@ -127,7 +135,7 @@ class LibraryImport { func importLIB(from filename: String) { do { - let documentDirectory = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create:false) + let documentDirectory = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create:false) let file = documentDirectory.appendingPathComponent(filename) @@ -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) } } diff --git a/Zockerhoehle/Views/GameDetailView.swift b/Zockerhoehle/Views/GameDetailView.swift index 4695119..5326b58 100644 --- a/Zockerhoehle/Views/GameDetailView.swift +++ b/Zockerhoehle/Views/GameDetailView.swift @@ -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 + + 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 @State var isImportingCover : Bool = false @@ -23,16 +54,25 @@ 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 GameIsFinished : some View { + Group { + Toggle(isOn: $game.isFinished , label: { + Text("Durchgezockt") + }) + + if game.isFinished { + Toggle(isOn: $hasFinishedDate, label: { + Text("Gibts ein Datum") + }) + } + + if hasFinishedDate && game.isFinished { + DatePicker("Durchgezockt am", + selection: $playthroughDate, + in: ...Date(), + displayedComponents: [.date]) + } + } } var body: some View { @@ -47,6 +87,16 @@ struct GameDetailView : View { 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") }) @@ -63,24 +113,9 @@ struct GameDetailView : View { TextField("Verliehen an", text: $game.lentTo ?? "") } - gameSeriesPicker + GameSeriesPicker(game: game) - Toggle(isOn: $game.isFinished , label: { - Text("Durchgezockt") - }) - - if game.isFinished { - Toggle(isOn: $hasFinishedDate, label: { - Text("Gibts ein Datum") - }) - } - - if hasFinishedDate && game.isFinished { - DatePicker("Durchgezockt am", - selection: $playthroughDate, - in: ...Date(), - displayedComponents: [.date]) - } + GameIsFinished } Section { @@ -89,9 +124,13 @@ struct GameDetailView : View { self.isImportingCover = true } - Image(uiImage: ICloudManager.imageFrom(path: game.cover_icloud_path) ?? defaultImage) - .resizable() - .frame(width:100, height: 100) + Group { + Image(uiImage: ICloudManager.imageFrom(path: game.cover_icloud_path) ?? defaultImage) + .resizable() + .scaledToFit() + }.frame(width:100, height: 100) + + } } diff --git a/Zockerhoehle/Views/Overview.swift b/Zockerhoehle/Views/Overview.swift index 1d74317..3874762 100644 --- a/Zockerhoehle/Views/Overview.swift +++ b/Zockerhoehle/Views/Overview.swift @@ -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) - .resizable() - .padding(10) - .cornerRadius(5) - }*/ - Text("\("Fetch Limit")") + Image(uiImage: ICloudManager.imageFrom(path: game.cover_icloud_path) ?? defaultImage) + .resizable() + .aspectRatio(contentMode: .fit) + .cornerRadius(5) } .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) - .resizable() - .padding(10) - .cornerRadius(5) - }*/ - Text("S") + Image(uiImage: ICloudManager.imageFrom(path: game_series.cover_icloud_path) ?? defaultImage) + .resizable() + .scaledToFit() + .cornerRadius(5) } - .frame(width: 100, height: 100) - .background(Color(UIColor.lightGray)) - .cornerRadius(5) + .frame(width: 150, height: 150) Text("\(game_series.name ?? "n/a")") .font(.caption) diff --git a/Zockerhoehle/Zockerhoehle.xcdatamodeld/Zockerhoehle.xcdatamodel/contents b/Zockerhoehle/Zockerhoehle.xcdatamodeld/Zockerhoehle.xcdatamodel/contents index b9c9bb9..6e71f25 100644 --- a/Zockerhoehle/Zockerhoehle.xcdatamodeld/Zockerhoehle.xcdatamodel/contents +++ b/Zockerhoehle/Zockerhoehle.xcdatamodeld/Zockerhoehle.xcdatamodel/contents @@ -33,6 +33,7 @@ + @@ -57,7 +58,7 @@ - + diff --git a/ZockerhoehleTests/test_date_utils.swift b/ZockerhoehleTests/test_date_utils.swift new file mode 100644 index 0000000..0a27eb0 --- /dev/null +++ b/ZockerhoehleTests/test_date_utils.swift @@ -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) + } + +}