Improved how game detail view look in non edit mode
@@ -19,6 +19,9 @@
|
||||
B94CB50422D1352F0029BFAD /* Game+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CB4F822D1352F0029BFAD /* Game+CoreDataProperties.swift */; };
|
||||
B94CB53722D3B3CC0029BFAD /* GameDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CB53622D3B3CC0029BFAD /* GameDetailView.swift */; };
|
||||
B952648526602CB600BB4324 /* MainView_Zockerhoehle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B952648426602CB600BB4324 /* MainView_Zockerhoehle.swift */; };
|
||||
B95264872661041D00BB4324 /* ConsoleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B95264862661041D00BB4324 /* ConsoleView.swift */; };
|
||||
B95264A826610E0800BB4324 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B95264A726610E0800BB4324 /* Preview Assets.xcassets */; };
|
||||
B969C5F0266693EE00401B89 /* GameEditMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B969C5EF266693EE00401B89 /* GameEditMode.swift */; };
|
||||
B9839983233A086A002F9946 /* Overview.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9839982233A086A002F9946 /* Overview.swift */; };
|
||||
B98A734D22BAD27D00FB3410 /* Zockerhoehle.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B98A731722BA9E4600FB3410 /* Zockerhoehle.xcdatamodeld */; };
|
||||
B98A736022C1738800FB3410 /* CDManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98A735F22C1738800FB3410 /* CDManager.swift */; };
|
||||
@@ -40,7 +43,7 @@
|
||||
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 */; };
|
||||
B9ED3DD9265D1E5600FD2D46 /* CDPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9ED3DD8265D1E5600FD2D46 /* CDPreview.swift */; };
|
||||
B9ED3DDB265D47EC00FD2D46 /* GameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9ED3DDA265D47EB00FD2D46 /* GameView.swift */; };
|
||||
B9ED3DDB265D47EC00FD2D46 /* GameCover.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9ED3DDA265D47EB00FD2D46 /* GameCover.swift */; };
|
||||
B9F44ABA22F312E600FC6B29 /* ConsoleLibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9F44AB922F312E600FC6B29 /* ConsoleLibraryView.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@@ -84,6 +87,9 @@
|
||||
B94CB4FA22D1352F0029BFAD /* Console+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Console+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
B94CB53622D3B3CC0029BFAD /* GameDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameDetailView.swift; sourceTree = "<group>"; };
|
||||
B952648426602CB600BB4324 /* MainView_Zockerhoehle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView_Zockerhoehle.swift; sourceTree = "<group>"; };
|
||||
B95264862661041D00BB4324 /* ConsoleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleView.swift; sourceTree = "<group>"; };
|
||||
B95264A726610E0800BB4324 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||
B969C5EF266693EE00401B89 /* GameEditMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameEditMode.swift; sourceTree = "<group>"; };
|
||||
B9839982233A086A002F9946 /* Overview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Overview.swift; sourceTree = "<group>"; };
|
||||
B98A731822BA9E4600FB3410 /* Zockerhoehle.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Zockerhoehle.xcdatamodel; sourceTree = "<group>"; };
|
||||
B98A735F22C1738800FB3410 /* CDManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CDManager.swift; sourceTree = "<group>"; };
|
||||
@@ -106,7 +112,7 @@
|
||||
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>"; };
|
||||
B9ED3DD8265D1E5600FD2D46 /* CDPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CDPreview.swift; sourceTree = "<group>"; };
|
||||
B9ED3DDA265D47EB00FD2D46 /* GameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameView.swift; sourceTree = "<group>"; };
|
||||
B9ED3DDA265D47EB00FD2D46 /* GameCover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameCover.swift; sourceTree = "<group>"; };
|
||||
B9F44AB922F312E600FC6B29 /* ConsoleLibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleLibraryView.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@@ -146,13 +152,15 @@
|
||||
B9A0550022F8C22D0054D9A0 /* GameSeriesAllView.swift */,
|
||||
B93D60CD22D88F5700DD390F /* AccessoryDetailView.swift */,
|
||||
B94CB53622D3B3CC0029BFAD /* GameDetailView.swift */,
|
||||
B9ED3DDA265D47EB00FD2D46 /* GameView.swift */,
|
||||
B969C5EF266693EE00401B89 /* GameEditMode.swift */,
|
||||
B9ED3DDA265D47EB00FD2D46 /* GameCover.swift */,
|
||||
B94112E3233B597D00159AE4 /* ConsoleEditView.swift */,
|
||||
B9F44AB922F312E600FC6B29 /* ConsoleLibraryView.swift */,
|
||||
B9E2A07C233B6E4F00EAEB14 /* ConsoleAllView.swift */,
|
||||
B94112DF233A4EF800159AE4 /* GamePickupsView.swift */,
|
||||
B9EC0986238555BF004BC9AB /* SettingsView.swift */,
|
||||
B9839982233A086A002F9946 /* Overview.swift */,
|
||||
B95264862661041D00BB4324 /* ConsoleView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
@@ -202,10 +210,19 @@
|
||||
B93C1BA321496BFE0014FD6E /* Assets.xcassets */,
|
||||
B93C1BA821496BFE0014FD6E /* Info.plist */,
|
||||
B98A731722BA9E4600FB3410 /* Zockerhoehle.xcdatamodeld */,
|
||||
B95264A626610E0800BB4324 /* Preview Content */,
|
||||
);
|
||||
path = Zockerhoehle;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B95264A626610E0800BB4324 /* Preview Content */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B95264A726610E0800BB4324 /* Preview Assets.xcassets */,
|
||||
);
|
||||
path = "Preview Content";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B98A734622BACA9C00FB3410 /* CDModel */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -323,6 +340,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B9BCF4CA2168ACB600ECBAAC /* LaunchScreen.storyboard in Resources */,
|
||||
B95264A826610E0800BB4324 /* Preview Assets.xcassets in Resources */,
|
||||
B93C1BA421496BFE0014FD6E /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -378,6 +396,7 @@
|
||||
B952648526602CB600BB4324 /* MainView_Zockerhoehle.swift in Sources */,
|
||||
B98CBBDC264E98DE00B1B7AC /* GameSeries+CoreDataClass.swift in Sources */,
|
||||
B9A0550122F8C22D0054D9A0 /* GameSeriesAllView.swift in Sources */,
|
||||
B969C5F0266693EE00401B89 /* GameEditMode.swift in Sources */,
|
||||
B94112E4233B597D00159AE4 /* ConsoleEditView.swift in Sources */,
|
||||
B94112DE233A37DD00159AE4 /* DateConversion.swift in Sources */,
|
||||
B93D60CE22D88F5700DD390F /* AccessoryDetailView.swift in Sources */,
|
||||
@@ -387,9 +406,10 @@
|
||||
B94CB50422D1352F0029BFAD /* Game+CoreDataProperties.swift in Sources */,
|
||||
B98A736022C1738800FB3410 /* CDManager.swift in Sources */,
|
||||
B9E2A07E233B6E4F00EAEB14 /* ConsoleAllView.swift in Sources */,
|
||||
B95264872661041D00BB4324 /* ConsoleView.swift in Sources */,
|
||||
B9BCCEB92653BDEA005F46D6 /* ICloudManager.swift in Sources */,
|
||||
B9EC0987238555BF004BC9AB /* SettingsView.swift in Sources */,
|
||||
B9ED3DDB265D47EC00FD2D46 /* GameView.swift in Sources */,
|
||||
B9ED3DDB265D47EC00FD2D46 /* GameCover.swift in Sources */,
|
||||
B9ED3DD9265D1E5600FD2D46 /* CDPreview.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -539,6 +559,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Zockerhoehle/Zockerhoehle.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_ASSET_PATHS = "Zockerhoehle/Preview\\ Content";
|
||||
DEVELOPMENT_TEAM = 85J8CBD673;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||
INFOPLIST_FILE = Zockerhoehle/Info.plist;
|
||||
@@ -550,6 +571,8 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = haus.mueller.zockerhoehle;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
@@ -562,6 +585,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Zockerhoehle/Zockerhoehle.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_ASSET_PATHS = "Zockerhoehle/Preview\\ Content";
|
||||
DEVELOPMENT_TEAM = M9N7K3KZX9;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||
INFOPLIST_FILE = Zockerhoehle/Info.plist;
|
||||
@@ -573,6 +597,8 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = haus.mueller.zockerhoehle;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ public class Console: NSManagedObject, Identifiable {
|
||||
Game.compareByCreationDate(gameA: $0, gameB: $1)
|
||||
}) else { return false }
|
||||
|
||||
return newestGameConsoleA.createdAt > newestGameConsoleB.createdAt
|
||||
return newestGameConsoleA.pickupOrReleaseDate > newestGameConsoleB.pickupOrReleaseDate
|
||||
}
|
||||
|
||||
// Defining default values during creation
|
||||
|
||||
@@ -15,14 +15,14 @@ import SwiftUI
|
||||
public class Game: NSManagedObject, Identifiable {
|
||||
|
||||
public static func compareByCreationDate(gameA : Game, gameB : Game) -> Bool {
|
||||
return gameA.createdAt < gameB.createdAt
|
||||
return gameA.pickupOrReleaseDate < gameB.pickupOrReleaseDate
|
||||
}
|
||||
|
||||
@objc
|
||||
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) {
|
||||
super.init(entity: entity, insertInto: context)
|
||||
|
||||
self.createdAt = Date()
|
||||
self.pickupOrReleaseDate = Date()
|
||||
self.uuid = UUID()
|
||||
print("Set UUID to \(self.uuid)")
|
||||
}
|
||||
@@ -35,7 +35,8 @@ extension Game : Encodable {
|
||||
case notes
|
||||
case isDigital
|
||||
case lentTo
|
||||
case createdAt
|
||||
case pickupOrReleaseDate
|
||||
case isPickupDate
|
||||
case publisher
|
||||
case isFinished
|
||||
case finishedDate
|
||||
@@ -55,7 +56,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(pickupOrReleaseDate.formattedInTimeZone(), forKey: .pickupOrReleaseDate)
|
||||
try container.encode(isPickupDate, forKey: .isPickupDate)
|
||||
try container.encode(pickupDescription ?? "", forKey: .pickupDescription)
|
||||
try container.encode(publisher ?? "", forKey: .publisher)
|
||||
try container.encode(isFinished, forKey: .isFinished)
|
||||
|
||||
@@ -18,7 +18,8 @@ extension Game {
|
||||
}
|
||||
|
||||
@NSManaged public var circumstances: String?
|
||||
@NSManaged public var createdAt: Date
|
||||
@NSManaged public var isPickupDate : Bool
|
||||
@NSManaged public var pickupOrReleaseDate: Date
|
||||
@NSManaged public var pickupDescription : String?
|
||||
@NSManaged public var inWishlist: Bool
|
||||
@NSManaged public var isDigital: Bool
|
||||
|
||||
@@ -32,4 +32,34 @@ struct CDPreview {
|
||||
}
|
||||
}
|
||||
|
||||
private func games(predicate: NSPredicate?) -> [Game] {
|
||||
let gameFR = NSFetchRequest<Game>(entityName: "Game")
|
||||
if let predicate = predicate {
|
||||
gameFR.predicate = predicate
|
||||
}
|
||||
do {
|
||||
let games : [Game] = try CDManager.preview.viewContextPreview.fetch(gameFR)
|
||||
|
||||
return games
|
||||
}catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
func games(console: Console) -> [Game] {
|
||||
return games(predicate: NSPredicate(format: "console = %@", console))
|
||||
}
|
||||
|
||||
func games() -> [Game] {
|
||||
return games(predicate: .none)
|
||||
}
|
||||
|
||||
func game(name: String) -> Game? {
|
||||
return self.games().first(where: {$0.name == name})
|
||||
}
|
||||
|
||||
func console(name: String) -> Console? {
|
||||
return self.consoles().first(where: {$0.name == name})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,6 +38,13 @@ class ICloudManager {
|
||||
return FileManager.default.fileExists(atPath: url.path)
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
let asset_name = path.replacingOccurrences(of: "/", with: "-")
|
||||
if UIImage(named: asset_name) != nil {
|
||||
return true
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -45,13 +52,31 @@ class ICloudManager {
|
||||
guard let path = path else { return .none }
|
||||
|
||||
do {
|
||||
let url = documents_folder?.appendingPathComponent(path)
|
||||
let imageData = try Data(contentsOf: url!)
|
||||
|
||||
return UIImage(data: imageData)!
|
||||
if let url = documents_folder?.appendingPathComponent(path) {
|
||||
let imageData = try Data(contentsOf: url)
|
||||
|
||||
return UIImage(data:imageData)
|
||||
}
|
||||
}catch {
|
||||
return .none
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
let asset_name = path.replacingOccurrences(of: "/", with: "-")
|
||||
if let image = UIImage(named: asset_name) {
|
||||
return image
|
||||
}
|
||||
#endif
|
||||
|
||||
return .none
|
||||
}
|
||||
|
||||
static public func imageRatio(path: String?) -> CGFloat {
|
||||
if let image = ICloudManager.imageFrom(path: path) {
|
||||
return image.size.width / image.size.height
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
static public func inICloudContainer(url : URL?) -> Bool {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "dreamcast.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Zockerhoehle/Preview Content/Preview Assets.xcassets/consoles-dreamcast.png.imageset/dreamcast.png
vendored
Normal file
|
After Width: | Height: | Size: 82 KiB |
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "gamecube.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Zockerhoehle/Preview Content/Preview Assets.xcassets/consoles-gamecube.png.imageset/gamecube.png
vendored
Normal file
|
After Width: | Height: | Size: 45 KiB |
21
Zockerhoehle/Preview Content/Preview Assets.xcassets/consoles-nes.png.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "nes.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Zockerhoehle/Preview Content/Preview Assets.xcassets/consoles-nes.png.imageset/nes.png
vendored
Normal file
|
After Width: | Height: | Size: 22 KiB |
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "nintendo_switch.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 107 KiB |
21
Zockerhoehle/Preview Content/Preview Assets.xcassets/consoles-ps2.png.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ps2.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Zockerhoehle/Preview Content/Preview Assets.xcassets/consoles-ps2.png.imageset/ps2.png
vendored
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
21
Zockerhoehle/Preview Content/Preview Assets.xcassets/consoles-ps3.png.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ps3.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Zockerhoehle/Preview Content/Preview Assets.xcassets/consoles-ps3.png.imageset/ps3.png
vendored
Normal file
|
After Width: | Height: | Size: 29 KiB |
21
Zockerhoehle/Preview Content/Preview Assets.xcassets/consoles-ps5.png.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ps5.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Zockerhoehle/Preview Content/Preview Assets.xcassets/consoles-ps5.png.imageset/ps5.png
vendored
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "xbox_one.jpg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Zockerhoehle/Preview Content/Preview Assets.xcassets/consoles-xbox_one.jpg.imageset/xbox_one.jpg
vendored
Normal file
|
After Width: | Height: | Size: 81 KiB |
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ocarina_of_time.jpg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 367 KiB |
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "zelda_breath_of_the_wild.jpg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.2 MiB |
@@ -9,8 +9,11 @@
|
||||
import Foundation
|
||||
|
||||
extension Date {
|
||||
static let zockerhoehle_date_format = "yyyy-MM-dd'T'HH:mm:ssZ"
|
||||
func formattedInTimeZone(timezone : TimeZone = .current) -> String {
|
||||
static let export_date_format = "yyyy-MM-dd'T'HH:mm:ssZ"
|
||||
|
||||
static let view_date_format = "dd.MM.yyyy"
|
||||
|
||||
func formattedInTimeZone(timezone : TimeZone = .current, dateFormat : String = Date.export_date_format) -> String {
|
||||
// 1) Create a DateFormatter() object.
|
||||
let format = DateFormatter()
|
||||
|
||||
@@ -18,7 +21,7 @@ extension Date {
|
||||
format.timeZone = timezone
|
||||
|
||||
// 3) Set the format of the altered date.
|
||||
format.dateFormat = Date.zockerhoehle_date_format
|
||||
format.dateFormat = dateFormat
|
||||
|
||||
// 4) Set the current date, altered by timezone.
|
||||
let dateString = format.string(from: self)
|
||||
@@ -29,7 +32,7 @@ extension Date {
|
||||
static func from(string : String, timezone : TimeZone = .current) -> Date? {
|
||||
let format = DateFormatter()
|
||||
format.timeZone = timezone
|
||||
format.dateFormat = Date.zockerhoehle_date_format
|
||||
format.dateFormat = Date.export_date_format
|
||||
|
||||
return format.date(from: string)
|
||||
}
|
||||
|
||||
@@ -92,11 +92,13 @@ class LibraryImport {
|
||||
cdGame.isDigital = game.isDigital
|
||||
cdGame.playtime_h = game.playtime_h ?? 0
|
||||
cdGame.playtime_min = game.playtime_min ?? 0
|
||||
cdGame.isPickupDate = game.isPickupDate
|
||||
|
||||
if let date = Date.from(string: game.createdAt) {
|
||||
cdGame.createdAt = date
|
||||
if let date = Date.from(string: game.pickupOrReleaseDate) {
|
||||
cdGame.pickupOrReleaseDate = date
|
||||
|
||||
}else{
|
||||
print("Could not decode date '\(game.createdAt)' for game '\(cdGame.name)'")
|
||||
print("Could not decode date '\(game.pickupOrReleaseDate)' for game '\(cdGame.name)'")
|
||||
}
|
||||
|
||||
cdConsole.addToGames(cdGame)
|
||||
@@ -218,7 +220,8 @@ struct BHLGame : Decodable {
|
||||
let isFinished : Bool
|
||||
let finishedDate : Date?
|
||||
let notes : String?
|
||||
let createdAt : String
|
||||
let pickupOrReleaseDate : String
|
||||
let isPickupDate : Bool = false
|
||||
let pickupDescription : String?
|
||||
let publisher : String?
|
||||
let console : UUID
|
||||
|
||||
@@ -116,7 +116,7 @@ struct ConsoleLibraryView : View {
|
||||
LazyVGrid(columns: columns, spacing: 20) {
|
||||
ForEach(games.filter({$0.inWishlist == self.showWishlist})) { game in
|
||||
NavigationLink(destination: GameDetailView(game: game)) {
|
||||
GameView(game: game)
|
||||
GameCover(game: game)
|
||||
}
|
||||
}
|
||||
}.padding()
|
||||
|
||||
54
Zockerhoehle/Views/ConsoleView.swift
Normal file
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// File.swift
|
||||
// Zockerhoehle
|
||||
//
|
||||
// Created by Julian-Steffen Müller on 28.05.21.
|
||||
// Copyright © 2021 Julian-Steffen Müller. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct ConsoleView : View {
|
||||
@ObservedObject var console : Console
|
||||
|
||||
public var defaultImage = UIImage()
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Rectangle()
|
||||
.fill(Color(red: 1, green: 0, blue: 0.03))
|
||||
|
||||
|
||||
Image(uiImage: ICloudManager.imageFrom(path: console.logo_icloud_path) ?? defaultImage)
|
||||
.resizable()
|
||||
.padding()
|
||||
.scaledToFit()
|
||||
|
||||
}
|
||||
.padding(.horizontal, 5)
|
||||
.aspectRatio(1.33, contentMode: .fit)
|
||||
.cornerRadius(15)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct ConsoleView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
|
||||
let consoles = CDPreview.shared.consoles()
|
||||
let console_whish = CDPreview.shared.console(name: "Xbox One")
|
||||
|
||||
guard let console = console_whish ?? consoles.first else {
|
||||
fatalError("No Console found in core data preview")
|
||||
}
|
||||
|
||||
var view = ConsoleView(console: console)
|
||||
view.defaultImage = UIImage(named: "PreviewConsole") ?? UIImage()
|
||||
|
||||
return Group {
|
||||
view
|
||||
view
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct GameView : View {
|
||||
struct GameCover : View {
|
||||
@ObservedObject var game : Game
|
||||
|
||||
let defaultImage = UIImage()
|
||||
@@ -34,3 +34,17 @@ struct GameView : View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct GameCover_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
|
||||
let game_wish = CDPreview.shared.game(name: "Zelda: Breath of the Wild")
|
||||
let games = CDPreview.shared.games()
|
||||
|
||||
guard let game = game_wish ?? games.first else {
|
||||
fatalError("No game found in core data preview")
|
||||
}
|
||||
|
||||
return GameCover(game: game)
|
||||
}
|
||||
}
|
||||
@@ -1,277 +1,151 @@
|
||||
//
|
||||
// GameDetailView.swift
|
||||
// Zockerhoehle
|
||||
//
|
||||
// Created by Julian-Steffen Müller on 08.07.19.
|
||||
// Copyright © 2019 Julian-Steffen Müller. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct GameSeriesPicker: View {
|
||||
@ObservedObject var game : Game
|
||||
struct HeadlinedMultilineText : View {
|
||||
let headline : String
|
||||
let multilineText : String
|
||||
|
||||
@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)
|
||||
VStack(spacing: 0) {
|
||||
HStack {
|
||||
Text(headline)
|
||||
.bold()
|
||||
.font(.headline)
|
||||
Spacer()
|
||||
}
|
||||
}.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
|
||||
}
|
||||
Divider()
|
||||
.padding(.bottom)
|
||||
|
||||
HStack{
|
||||
Text(multilineText)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(game : Game) {
|
||||
self.game = game
|
||||
|
||||
if let game_series = game.series {
|
||||
_selectedGameSeries = State(initialValue: game_series.uuid.uuidString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AttachmentCellImage : View {
|
||||
@ObservedObject var game : Game
|
||||
var onClick: ()->()
|
||||
|
||||
let defaultImage = UIImage()
|
||||
struct InfoItem<Content : View> : View {
|
||||
let headline : String
|
||||
let content : () -> Content
|
||||
|
||||
var body: some View {
|
||||
HStack{
|
||||
Button("Cover") {
|
||||
self.onClick()
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Group {
|
||||
Image(uiImage: ICloudManager.imageFrom(path: game.cover_icloud_path) ?? defaultImage)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
}.frame(width:100, height: 100)
|
||||
VStack {
|
||||
Text("Spielzeit")
|
||||
.padding(.bottom)
|
||||
.foregroundColor(.gray)
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct GameView : View {
|
||||
@ObservedObject var game : Game
|
||||
|
||||
let columns = [
|
||||
GridItem(.flexible()),
|
||||
GridItem(.flexible()),
|
||||
GridItem(.flexible())
|
||||
]
|
||||
|
||||
private func coverPadding(game: Game) -> CGFloat {
|
||||
if ICloudManager.imageRatio(path: game.cover_icloud_path) < 1 {
|
||||
return 30
|
||||
}else{
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
init(game: Game, onClick: @escaping ()->()) {
|
||||
self.game = game
|
||||
self.onClick = onClick
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack {
|
||||
GameCover(game: game)
|
||||
.padding(.horizontal, coverPadding(game: game))
|
||||
Text("\(game.name)")
|
||||
.font(.title)
|
||||
Text("\(game.console?.name ?? "No Console")")
|
||||
.foregroundColor(.gray)
|
||||
|
||||
|
||||
LazyVGrid(columns: columns) {
|
||||
InfoItem(headline: "Gekauft") {
|
||||
Text("\(game.pickupOrReleaseDate.formattedInTimeZone(dateFormat: Date.view_date_format))")
|
||||
.bold()
|
||||
}
|
||||
|
||||
if game.playtime_h > 0 || game.playtime_min > 0 {
|
||||
InfoItem(headline: "Spielzeit") {
|
||||
Text("\(game.playtime_h)h \(game.playtime_min)min")
|
||||
}
|
||||
}
|
||||
|
||||
if game.isFinished {
|
||||
InfoItem(headline: "Durchgezockt") {
|
||||
Text("Ja")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.padding(.vertical)
|
||||
|
||||
HeadlinedMultilineText(headline: "Notizen", multilineText: "Ganz toller multiline\nText")
|
||||
.padding(.bottom)
|
||||
|
||||
HeadlinedMultilineText(headline: "Hinter den Kulissen", multilineText: "Ich habe für das Spiel bei MCMedia Games angestanden")
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}.padding(.horizontal, 40)
|
||||
}
|
||||
}
|
||||
|
||||
struct GameView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
|
||||
let game_wish = CDPreview.shared.game(name: "Zelda - Ocarina of Time")
|
||||
let games = CDPreview.shared.games()
|
||||
|
||||
guard let game = game_wish ?? games.first else {
|
||||
fatalError("No game found in core data preview")
|
||||
}
|
||||
|
||||
return GameView(game: game)
|
||||
}
|
||||
}
|
||||
|
||||
struct GameView_Landscape_Cover_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
|
||||
let game_wish = CDPreview.shared.game(name: "Zelda: Breath of the Wild")
|
||||
let games = CDPreview.shared.games()
|
||||
|
||||
guard let game = game_wish ?? games.first else {
|
||||
fatalError("No game found in core data preview")
|
||||
}
|
||||
|
||||
return GameView(game: game)
|
||||
}
|
||||
}
|
||||
|
||||
struct GameDetailView : View {
|
||||
@ObservedObject var game : Game
|
||||
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
|
||||
|
||||
@State private var showDeleteAlert : Bool = false
|
||||
|
||||
@State var isImportingCover : Bool = false
|
||||
|
||||
let defaultImage = UIImage()
|
||||
|
||||
@State var isLent_raw : Bool = false
|
||||
private var isLent: Binding<Bool> {
|
||||
Binding<Bool>(
|
||||
get: { self.isLent_raw || (self.game.lentTo != .none && self.game.lentTo != "") },
|
||||
set: {
|
||||
isLent_raw = $0
|
||||
if !$0 { self.game.lentTo = .none }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@State var hasFinishedDate_raw : Bool = false
|
||||
private var hasFinishedDate: Binding<Bool> {
|
||||
Binding<Bool>(
|
||||
get: { self.hasFinishedDate_raw || self.game.finishedDate != .none },
|
||||
set: {
|
||||
hasFinishedDate_raw = $0
|
||||
if !$0 { self.game.finishedDate = .none }
|
||||
}
|
||||
)
|
||||
}
|
||||
var finishedDateBinding: Binding<Date> {
|
||||
Binding<Date>(
|
||||
get: { self.game.finishedDate ?? Date() },
|
||||
set: { self.game.finishedDate = $0 })
|
||||
}
|
||||
|
||||
|
||||
var notesBinding: Binding<String> {
|
||||
Binding<String>(
|
||||
get: { self.game.notes ?? "" },
|
||||
set: { self.game.notes = $0 })
|
||||
}
|
||||
|
||||
var pickupDscriptionBinding: Binding<String> {
|
||||
Binding<String>(
|
||||
get: { self.game.pickupDescription ?? "" },
|
||||
set: { self.game.pickupDescription = $0 })
|
||||
}
|
||||
|
||||
var lentToBinding: Binding<String> {
|
||||
Binding<String>(
|
||||
get: { self.game.lentTo ?? "" },
|
||||
set: { self.game.lentTo = $0 })
|
||||
}
|
||||
|
||||
@State var showWrongFolderAlert : Bool = false
|
||||
var GameIsFinished : some View {
|
||||
Group {
|
||||
Toggle(isOn: $game.isFinished , label: {
|
||||
Text("Durchgezockt")
|
||||
}).onChange(of: game.isFinished) {
|
||||
if !$0 {
|
||||
game.playtime_h = 0
|
||||
game.playtime_min = 0
|
||||
}
|
||||
}
|
||||
|
||||
if game.isFinished {
|
||||
HStack {
|
||||
Stepper("\(game.playtime_h)h", value: $game.playtime_h, in: 0...200)
|
||||
Stepper("\(game.playtime_min)min", value: $game.playtime_min, in: 0...60)
|
||||
}
|
||||
|
||||
Toggle(isOn: hasFinishedDate, label: {
|
||||
Text("Gibts ein Datum")
|
||||
})
|
||||
}
|
||||
|
||||
if self.hasFinishedDate.wrappedValue && game.isFinished {
|
||||
DatePicker("Durchgezockt am",
|
||||
selection: finishedDateBinding,
|
||||
in: ...Date(),
|
||||
displayedComponents: [.date])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@State private var isInEditMode = false
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
TextField("Videogame name", text: $game.name)
|
||||
|
||||
//Gray color should indicate immutable data
|
||||
|
||||
HStack {
|
||||
Text("Konsole")
|
||||
Text("\(game.console?.name ?? "No console")").foregroundColor(.gray)
|
||||
}
|
||||
|
||||
Toggle(isOn: $game.isDigital, label: {
|
||||
Text("Nur Digital")
|
||||
})
|
||||
|
||||
|
||||
|
||||
Toggle(isOn: $game.inWishlist, label: {
|
||||
Text("In Wunschliste")
|
||||
})
|
||||
}
|
||||
|
||||
Section(header: Text("Details")) {
|
||||
DatePicker("In Sammlung seit",
|
||||
selection: $game.createdAt,
|
||||
in: ...Date(),
|
||||
displayedComponents: [.date])
|
||||
|
||||
HStack {
|
||||
Text("Anlass")
|
||||
TextEditor(text: pickupDscriptionBinding).frame(height: 100)
|
||||
}
|
||||
|
||||
GameSeriesPicker(game: game)
|
||||
|
||||
Toggle(isOn: isLent, label: {
|
||||
Text("Verliehen?")
|
||||
})
|
||||
|
||||
if isLent.wrappedValue {
|
||||
TextField("Verliehen an", text: lentToBinding)
|
||||
}
|
||||
|
||||
GameIsFinished
|
||||
|
||||
}
|
||||
|
||||
Section(header: Text("Notizen")) {
|
||||
TextEditor(text: notesBinding).frame(height: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/)
|
||||
}
|
||||
|
||||
Section(header: Text("Anhänge")){
|
||||
List {
|
||||
AttachmentCellImage(game: game) {
|
||||
self.isImportingCover = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section{
|
||||
gameDeleteButton
|
||||
}
|
||||
|
||||
}
|
||||
.navigationBarTitle(Text("\(game.name)"), displayMode: .automatic)
|
||||
.fileImporter(
|
||||
isPresented: $isImportingCover,
|
||||
allowedContentTypes: [.jpeg, .png],
|
||||
allowsMultipleSelection: false
|
||||
) { result in
|
||||
do {
|
||||
let selectedFile : URL = try result.get().first!
|
||||
|
||||
//It seems that isUbiquitousItem checks if the file is contained in the Apps iCloud folder
|
||||
if (ICloudManager.inICloudContainer(url: selectedFile)) {
|
||||
game.cover_icloud_path = ICloudManager.relativePathFrom(url: selectedFile)
|
||||
|
||||
print("Selected Image in iCloud Path \(game.cover_icloud_path ?? "n/a")")
|
||||
}else{
|
||||
self.showWrongFolderAlert = true
|
||||
}
|
||||
}catch{
|
||||
print("ConsoleAllView::ModalAddConsoleToLibrary Error getting result '\(result)'")
|
||||
Group {
|
||||
if !isInEditMode {
|
||||
GameView(game: game)
|
||||
}else {
|
||||
GameEditMode(game: game)
|
||||
}
|
||||
}
|
||||
.alert(isPresented: $showWrongFolderAlert) {
|
||||
Alert(
|
||||
title: Text("Falscher Ordner"),
|
||||
message: Text("Bitte nutze nur Dateien aus dem Zockerhöhle iCloud Ordner"),
|
||||
dismissButton: .default(Text("Alles klar!"))
|
||||
)
|
||||
.navigationTitle("\(game.name)")
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||
Button(action: {
|
||||
isInEditMode.toggle()
|
||||
},
|
||||
label: { Image(systemName: "plus")})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Irgendwie gibt es einen Bug der den Alert im navigationBarItem zwei mal aufruft.
|
||||
//Deswegen muss der delete Button is auf weiteres in die Form
|
||||
var gameDeleteButton : some View {
|
||||
Button(action: {
|
||||
self.showDeleteAlert = true
|
||||
}, label: {
|
||||
Spacer()
|
||||
Text("Spiel löschen")
|
||||
Spacer()
|
||||
//Image(systemName: "trash").foregroundColor(.red)
|
||||
})
|
||||
.accentColor(.red)
|
||||
.alert(isPresented: $showDeleteAlert, content: {
|
||||
Alert(title: Text("Aus Zockerhöhle entfernen"), message: Text("Willst du '\(self.game.name)' wirklich aus der Zockerhöhle werfen?"), primaryButton: Alert.Button.destructive(Text("Ja!"), action: {
|
||||
//
|
||||
self.presentationMode.wrappedValue.dismiss()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
|
||||
CDManager.shared.viewContext.delete(game)
|
||||
}
|
||||
|
||||
}), secondaryButton: Alert.Button.cancel(Text("Lieber doch nicht")))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
277
Zockerhoehle/Views/GameEditMode.swift
Normal file
@@ -0,0 +1,277 @@
|
||||
//
|
||||
// GameDetailView.swift
|
||||
// Zockerhoehle
|
||||
//
|
||||
// Created by Julian-Steffen Müller on 08.07.19.
|
||||
// Copyright © 2019 Julian-Steffen Müller. All rights reserved.
|
||||
//
|
||||
|
||||
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 AttachmentCellImage : View {
|
||||
@ObservedObject var game : Game
|
||||
var onClick: ()->()
|
||||
|
||||
let defaultImage = UIImage()
|
||||
|
||||
var body: some View {
|
||||
HStack{
|
||||
Button("Cover") {
|
||||
self.onClick()
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Group {
|
||||
Image(uiImage: ICloudManager.imageFrom(path: game.cover_icloud_path) ?? defaultImage)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
}.frame(width:100, height: 100)
|
||||
}
|
||||
}
|
||||
|
||||
init(game: Game, onClick: @escaping ()->()) {
|
||||
self.game = game
|
||||
self.onClick = onClick
|
||||
}
|
||||
}
|
||||
|
||||
struct GameEditMode : View {
|
||||
@ObservedObject var game : Game
|
||||
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
|
||||
|
||||
@State private var showDeleteAlert : Bool = false
|
||||
|
||||
@State var isImportingCover : Bool = false
|
||||
|
||||
let defaultImage = UIImage()
|
||||
|
||||
@State var isLent_raw : Bool = false
|
||||
private var isLent: Binding<Bool> {
|
||||
Binding<Bool>(
|
||||
get: { self.isLent_raw || (self.game.lentTo != .none && self.game.lentTo != "") },
|
||||
set: {
|
||||
isLent_raw = $0
|
||||
if !$0 { self.game.lentTo = .none }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@State var hasFinishedDate_raw : Bool = false
|
||||
private var hasFinishedDate: Binding<Bool> {
|
||||
Binding<Bool>(
|
||||
get: { self.hasFinishedDate_raw || self.game.finishedDate != .none },
|
||||
set: {
|
||||
hasFinishedDate_raw = $0
|
||||
if !$0 { self.game.finishedDate = .none }
|
||||
}
|
||||
)
|
||||
}
|
||||
var finishedDateBinding: Binding<Date> {
|
||||
Binding<Date>(
|
||||
get: { self.game.finishedDate ?? Date() },
|
||||
set: { self.game.finishedDate = $0 })
|
||||
}
|
||||
|
||||
|
||||
var notesBinding: Binding<String> {
|
||||
Binding<String>(
|
||||
get: { self.game.notes ?? "" },
|
||||
set: { self.game.notes = $0 })
|
||||
}
|
||||
|
||||
var pickupDscriptionBinding: Binding<String> {
|
||||
Binding<String>(
|
||||
get: { self.game.pickupDescription ?? "" },
|
||||
set: { self.game.pickupDescription = $0 })
|
||||
}
|
||||
|
||||
var lentToBinding: Binding<String> {
|
||||
Binding<String>(
|
||||
get: { self.game.lentTo ?? "" },
|
||||
set: { self.game.lentTo = $0 })
|
||||
}
|
||||
|
||||
@State var showWrongFolderAlert : Bool = false
|
||||
var GameIsFinished : some View {
|
||||
Group {
|
||||
Toggle(isOn: $game.isFinished , label: {
|
||||
Text("Durchgezockt")
|
||||
}).onChange(of: game.isFinished) {
|
||||
if !$0 {
|
||||
game.playtime_h = 0
|
||||
game.playtime_min = 0
|
||||
}
|
||||
}
|
||||
|
||||
if game.isFinished {
|
||||
HStack {
|
||||
Stepper("\(game.playtime_h)h", value: $game.playtime_h, in: 0...200)
|
||||
Stepper("\(game.playtime_min)min", value: $game.playtime_min, in: 0...60)
|
||||
}
|
||||
|
||||
Toggle(isOn: hasFinishedDate, label: {
|
||||
Text("Gibts ein Datum")
|
||||
})
|
||||
}
|
||||
|
||||
if self.hasFinishedDate.wrappedValue && game.isFinished {
|
||||
DatePicker("Durchgezockt am",
|
||||
selection: finishedDateBinding,
|
||||
in: ...Date(),
|
||||
displayedComponents: [.date])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
TextField("Videogame name", text: $game.name)
|
||||
|
||||
//Gray color should indicate immutable data
|
||||
|
||||
HStack {
|
||||
Text("Konsole")
|
||||
Text("\(game.console?.name ?? "No console")").foregroundColor(.gray)
|
||||
}
|
||||
|
||||
Toggle(isOn: $game.isDigital, label: {
|
||||
Text("Nur Digital")
|
||||
})
|
||||
|
||||
|
||||
|
||||
Toggle(isOn: $game.inWishlist, label: {
|
||||
Text("In Wunschliste")
|
||||
})
|
||||
}
|
||||
|
||||
Section(header: Text("Details")) {
|
||||
DatePicker("In Sammlung seit",
|
||||
selection: $game.pickupOrReleaseDate,
|
||||
in: ...Date(),
|
||||
displayedComponents: [.date])
|
||||
|
||||
HStack {
|
||||
Text("Anlass")
|
||||
TextEditor(text: pickupDscriptionBinding).frame(height: 100)
|
||||
}
|
||||
|
||||
GameSeriesPicker(game: game)
|
||||
|
||||
Toggle(isOn: isLent, label: {
|
||||
Text("Verliehen?")
|
||||
})
|
||||
|
||||
if isLent.wrappedValue {
|
||||
TextField("Verliehen an", text: lentToBinding)
|
||||
}
|
||||
|
||||
GameIsFinished
|
||||
|
||||
}
|
||||
|
||||
Section(header: Text("Notizen")) {
|
||||
TextEditor(text: notesBinding).frame(height: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/)
|
||||
}
|
||||
|
||||
Section(header: Text("Anhänge")){
|
||||
List {
|
||||
AttachmentCellImage(game: game) {
|
||||
self.isImportingCover = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section{
|
||||
gameDeleteButton
|
||||
}
|
||||
|
||||
}
|
||||
.navigationBarTitle(Text("\(game.name)"), displayMode: .automatic)
|
||||
.fileImporter(
|
||||
isPresented: $isImportingCover,
|
||||
allowedContentTypes: [.jpeg, .png],
|
||||
allowsMultipleSelection: false
|
||||
) { result in
|
||||
do {
|
||||
let selectedFile : URL = try result.get().first!
|
||||
|
||||
//It seems that isUbiquitousItem checks if the file is contained in the Apps iCloud folder
|
||||
if (ICloudManager.inICloudContainer(url: selectedFile)) {
|
||||
game.cover_icloud_path = ICloudManager.relativePathFrom(url: selectedFile)
|
||||
|
||||
print("Selected Image in iCloud Path \(game.cover_icloud_path ?? "n/a")")
|
||||
}else{
|
||||
self.showWrongFolderAlert = true
|
||||
}
|
||||
}catch{
|
||||
print("ConsoleAllView::ModalAddConsoleToLibrary Error getting result '\(result)'")
|
||||
}
|
||||
}
|
||||
.alert(isPresented: $showWrongFolderAlert) {
|
||||
Alert(
|
||||
title: Text("Falscher Ordner"),
|
||||
message: Text("Bitte nutze nur Dateien aus dem Zockerhöhle iCloud Ordner"),
|
||||
dismissButton: .default(Text("Alles klar!"))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//Irgendwie gibt es einen Bug der den Alert im navigationBarItem zwei mal aufruft.
|
||||
//Deswegen muss der delete Button is auf weiteres in die Form
|
||||
var gameDeleteButton : some View {
|
||||
Button(action: {
|
||||
self.showDeleteAlert = true
|
||||
}, label: {
|
||||
Spacer()
|
||||
Text("Spiel löschen")
|
||||
Spacer()
|
||||
//Image(systemName: "trash").foregroundColor(.red)
|
||||
})
|
||||
.accentColor(.red)
|
||||
.alert(isPresented: $showDeleteAlert, content: {
|
||||
Alert(title: Text("Aus Zockerhöhle entfernen"), message: Text("Willst du '\(self.game.name)' wirklich aus der Zockerhöhle werfen?"), primaryButton: Alert.Button.destructive(Text("Ja!"), action: {
|
||||
//
|
||||
self.presentationMode.wrappedValue.dismiss()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
|
||||
CDManager.shared.viewContext.delete(game)
|
||||
}
|
||||
|
||||
}), secondaryButton: Alert.Button.cancel(Text("Lieber doch nicht")))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ struct GamePickupsView: View {
|
||||
|
||||
init() {
|
||||
let gamesFR = NSFetchRequest<Game>(entityName: "Game")
|
||||
gamesFR.sortDescriptors = [NSSortDescriptor(key: "createdAt", ascending: false), NSSortDescriptor(key: "name", ascending: true)]
|
||||
gamesFR.sortDescriptors = [NSSortDescriptor(key: "pickupOrReleaseDate", ascending: false), NSSortDescriptor(key: "name", ascending: true)]
|
||||
gamesFR.fetchLimit = 50;
|
||||
_games = FetchRequest(fetchRequest: gamesFR)
|
||||
}
|
||||
@@ -29,8 +29,8 @@ struct GamePickupsView: View {
|
||||
ScrollView {
|
||||
LazyVGrid(columns: columns, spacing: 20) {
|
||||
ForEach(games) { game in
|
||||
NavigationLink(destination: GameDetailView(game: game)) {
|
||||
GameView(game: game)
|
||||
NavigationLink(destination: GameEditMode(game: game)) {
|
||||
GameCover(game: game)
|
||||
}
|
||||
}
|
||||
}.padding()
|
||||
|
||||
@@ -35,8 +35,8 @@ struct GameSeriesLibraryView: View {
|
||||
ScrollView {
|
||||
LazyVGrid(columns: columns, spacing: 20) {
|
||||
ForEach(games) { game in
|
||||
NavigationLink(destination: GameDetailView(game: game)) {
|
||||
GameView(game: game)
|
||||
NavigationLink(destination: GameEditMode(game: game)) {
|
||||
GameCover(game: game)
|
||||
}
|
||||
}
|
||||
}.padding()
|
||||
|
||||
@@ -54,7 +54,7 @@ struct EmptyCategory : View {
|
||||
struct Overview: View {
|
||||
@Environment(\.managedObjectContext) private var viewContext
|
||||
|
||||
@FetchRequest(entity: Game.entity(), sortDescriptors: [NSSortDescriptor(key: "createdAt", ascending: false), NSSortDescriptor(key: "name", ascending: true)])
|
||||
@FetchRequest(entity: Game.entity(), sortDescriptors: [NSSortDescriptor(key: "pickupOrReleaseDate", ascending: false), NSSortDescriptor(key: "name", ascending: true)])
|
||||
var games: FetchedResults<Game>
|
||||
|
||||
@FetchRequest(entity: Game.entity(), sortDescriptors: [NSSortDescriptor(key: "finishedDate", ascending: false), NSSortDescriptor(key: "name", ascending: true)])
|
||||
@@ -115,10 +115,10 @@ struct Overview: View {
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(alignment: .top, spacing: 0) {
|
||||
ForEach(games.prefix(last_picksups_limit)) { game in
|
||||
NavigationLink(destination: GameDetailView(game: game)) {
|
||||
NavigationLink(destination: GameEditMode(game: game)) {
|
||||
VStack(alignment: .leading) {
|
||||
Group {
|
||||
GameView(game: game)
|
||||
GameCover(game: game)
|
||||
}
|
||||
.frame(width: 100, height: 100)
|
||||
|
||||
@@ -145,10 +145,10 @@ struct Overview: View {
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(alignment: .top, spacing: 0) {
|
||||
ForEach(gamesFinished.prefix(last_picksups_limit).filter({$0.isFinished})) { game in
|
||||
NavigationLink(destination: GameDetailView(game: game)) {
|
||||
NavigationLink(destination: GameEditMode(game: game)) {
|
||||
VStack(alignment: .leading) {
|
||||
Group {
|
||||
GameView(game: game)
|
||||
GameCover(game: game)
|
||||
}
|
||||
.frame(width: 100, height: 100)
|
||||
|
||||
|
||||
@@ -17,5 +17,9 @@
|
||||
<array>
|
||||
<string>iCloud.Zockerhoehle</string>
|
||||
</array>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -23,15 +23,16 @@
|
||||
</entity>
|
||||
<entity name="Game" representedClassName="Game" syncable="YES">
|
||||
<attribute name="cover_icloud_path" optional="YES" attributeType="String"/>
|
||||
<attribute name="createdAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="finishedDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="inWishlist" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="isDigital" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="isFinished" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="isPickupDate" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<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="pickupOrReleaseDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="playtime_h" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="playtime_min" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="publisher" optional="YES" attributeType="String"/>
|
||||
@@ -54,7 +55,7 @@
|
||||
<elements>
|
||||
<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="Game" positionX="-288.828125" positionY="276.7421875" width="128" height="290"/>
|
||||
<element name="Game" positionX="-288.828125" positionY="276.7421875" width="128" height="305"/>
|
||||
<element name="GameSeries" positionX="-686.828125" positionY="359.20703125" width="128" height="89"/>
|
||||
<element name="GameSeriesCover" positionX="-477" positionY="180" width="128" height="44"/>
|
||||
</elements>
|
||||
|
||||