Refactors GamesViewController & GameCollectionViewController (previously GamesCollectionViewController)
This commit is contained in:
parent
9e66df0ace
commit
21c5d13ba2
@ -1,126 +0,0 @@
|
||||
//
|
||||
// GameCollectionViewDataSource.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 10/30/15.
|
||||
// Copyright © 2015 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
|
||||
class GameCollectionViewDataSource: NSObject
|
||||
{
|
||||
var supportedGameCollectionIdentifiers: [String]? {
|
||||
didSet
|
||||
{
|
||||
self.updateFetchedResultsController()
|
||||
}
|
||||
}
|
||||
|
||||
var cellConfigurationHandler: ((GridCollectionViewCell, Game) -> Void)?
|
||||
|
||||
private(set) var fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult> = NSFetchedResultsController<NSFetchRequestResult>()
|
||||
|
||||
private var prototypeCell = GridCollectionViewCell()
|
||||
|
||||
private var _registeredCollectionViewCells = false
|
||||
|
||||
// MARK: - Update -
|
||||
|
||||
func update()
|
||||
{
|
||||
do
|
||||
{
|
||||
try self.fetchedResultsController.performFetch()
|
||||
}
|
||||
catch let error as NSError
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateFetchedResultsController()
|
||||
{
|
||||
let previousDelegate = self.fetchedResultsController.delegate
|
||||
|
||||
let fetchRequest = Game.rst_fetchRequest()
|
||||
|
||||
var predicates: [NSPredicate] = []
|
||||
|
||||
if let identifiers = self.supportedGameCollectionIdentifiers
|
||||
{
|
||||
for identifier in identifiers
|
||||
{
|
||||
let predicate = NSPredicate(format: "SUBQUERY(%K, $x, $x.%K == %@).@count > 0", Game.Attributes.gameCollections.rawValue, GameCollection.Attributes.identifier.rawValue, identifier)
|
||||
predicates.append(predicate)
|
||||
}
|
||||
}
|
||||
|
||||
if predicates.count > 0
|
||||
{
|
||||
fetchRequest.predicate = NSCompoundPredicate(orPredicateWithSubpredicates: predicates)
|
||||
}
|
||||
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: Game.Attributes.type.rawValue, ascending: true), NSSortDescriptor(key: Game.Attributes.name.rawValue, ascending: true)]
|
||||
|
||||
self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext, sectionNameKeyPath: Game.Attributes.type.rawValue, cacheName: nil)
|
||||
self.fetchedResultsController.delegate = previousDelegate
|
||||
|
||||
self.update()
|
||||
}
|
||||
|
||||
// MARK: - Collection View -
|
||||
|
||||
private func configureCell(_ cell: GridCollectionViewCell, forIndexPath indexPath: IndexPath)
|
||||
{
|
||||
let game = self.fetchedResultsController.object(at: indexPath) as! Game
|
||||
|
||||
if let handler = self.cellConfigurationHandler
|
||||
{
|
||||
handler(cell, game)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension GameCollectionViewDataSource: UICollectionViewDataSource
|
||||
{
|
||||
func numberOfSections(in collectionView: UICollectionView) -> Int
|
||||
{
|
||||
return self.fetchedResultsController.sections?.count ?? 0
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
|
||||
{
|
||||
let count = self.fetchedResultsController.sections?[section].numberOfObjects ?? 0
|
||||
return count
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
|
||||
{
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "GameCell", for: indexPath) as! GridCollectionViewCell
|
||||
|
||||
self.configureCell(cell, forIndexPath: indexPath)
|
||||
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
extension GameCollectionViewDataSource: UICollectionViewDelegate
|
||||
{
|
||||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize
|
||||
{
|
||||
let collectionViewLayout = collectionView.collectionViewLayout as! GridCollectionViewLayout
|
||||
|
||||
let widthConstraint = self.prototypeCell.contentView.widthAnchor.constraint(equalToConstant: collectionViewLayout.itemWidth)
|
||||
widthConstraint.isActive = true
|
||||
|
||||
self.configureCell(self.prototypeCell, forIndexPath: indexPath)
|
||||
|
||||
let size = self.prototypeCell.contentView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
|
||||
|
||||
widthConstraint.isActive = false
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
@ -10,6 +10,6 @@
|
||||
|
||||
@interface NSFetchedResultsController (Conveniences)
|
||||
|
||||
- (void)performFetchIfNeeded;
|
||||
- (BOOL)performFetchIfNeeded;
|
||||
|
||||
@end
|
||||
|
||||
@ -12,20 +12,21 @@
|
||||
|
||||
@implementation NSFetchedResultsController (Conveniences)
|
||||
|
||||
// Needs to be implemented in Objective-C due to current limitation of Swift:
|
||||
// Extension of a generic Objective-C class cannot access the class's generic parameters at runtime
|
||||
- (void)performFetchIfNeeded
|
||||
// Needs to be implemented in Objective-C, because it crashes the Swift compiler :(
|
||||
- (BOOL)performFetchIfNeeded
|
||||
{
|
||||
if (self.fetchedObjects != nil)
|
||||
if (self.sections != nil)
|
||||
{
|
||||
return;
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
NSError *error = nil;
|
||||
if (![self performFetch:&error])
|
||||
{
|
||||
ELog(error);
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
BF02BD001D361BD1000892F2 /* NSFetchedResultsController+Conveniences.m in Sources */ = {isa = PBXBuildFile; fileRef = BF02BCFF1D361BD1000892F2 /* NSFetchedResultsController+Conveniences.m */; };
|
||||
BF0418141D01E93400E85BCF /* GBADeltaCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF0418131D01E93400E85BCF /* GBADeltaCore.framework */; };
|
||||
BF0418151D01E93400E85BCF /* GBADeltaCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF0418131D01E93400E85BCF /* GBADeltaCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
BF090CF41B490D8300DCAB45 /* UIDevice+Vibration.m in Sources */ = {isa = PBXBuildFile; fileRef = BF090CF31B490D8300DCAB45 /* UIDevice+Vibration.m */; };
|
||||
@ -18,7 +17,6 @@
|
||||
BF172AEB1C68986300C26774 /* NSManagedObjectContext+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF172AEA1C68986300C26774 /* NSManagedObjectContext+Conveniences.swift */; };
|
||||
BF1FB1861C5EE643007E2494 /* SaveState.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1FB1831C5EE643007E2494 /* SaveState.swift */; };
|
||||
BF27CC8E1BC9FEA200A20D89 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF6BB2451BB73FE800CCF94A /* Assets.xcassets */; };
|
||||
BF27CC971BCC890700A20D89 /* GamesCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF27CC961BCC890700A20D89 /* GamesCollectionViewController.swift */; };
|
||||
BF2B98E61C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF2B98E51C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift */; };
|
||||
BF31878B1D489AAA00BD020D /* CheatValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF31878A1D489AAA00BD020D /* CheatValidator.swift */; };
|
||||
BF34FA071CF0F510006624C7 /* EditCheatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF34FA061CF0F510006624C7 /* EditCheatViewController.swift */; };
|
||||
@ -50,13 +48,14 @@
|
||||
BFA2315C1CED10BE0011E35A /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA2315B1CED10BE0011E35A /* Action.swift */; };
|
||||
BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAA1FEC1B8AA4FA00495943 /* Settings.swift */; };
|
||||
BFAA1FF41B8AD7F900495943 /* ControllersSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAA1FF31B8AD7F900495943 /* ControllersSettingsViewController.swift */; };
|
||||
BFB141181BE46934004FBF46 /* GameCollectionViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB141171BE46934004FBF46 /* GameCollectionViewDataSource.swift */; };
|
||||
BFBAA86A1D5A483900A29C1B /* DatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFBAA8691D5A483900A29C1B /* DatabaseManager.swift */; };
|
||||
BFC2731A1BE6152200D22B05 /* GameCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC273171BE6152200D22B05 /* GameCollection.swift */; };
|
||||
BFC9B7391CEFCD34008629BB /* CheatsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC9B7381CEFCD34008629BB /* CheatsViewController.swift */; };
|
||||
BFCEA67E1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFCEA67D1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift */; };
|
||||
BFD097211D3A01B8005A44C2 /* SaveStatesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3540041C5DA70400C1184C /* SaveStatesViewController.swift */; };
|
||||
BFDB28451BC9DA7B001D0C83 /* GamePickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB28441BC9DA7B001D0C83 /* GamePickerController.swift */; };
|
||||
BFDD04EF1D5E27DB002D450E /* NSFetchedResultsController+Conveniences.m in Sources */ = {isa = PBXBuildFile; fileRef = BF02BCFF1D361BD1000892F2 /* NSFetchedResultsController+Conveniences.m */; };
|
||||
BFDD04F11D5E2C27002D450E /* GameCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDD04F01D5E2C27002D450E /* GameCollectionViewController.swift */; };
|
||||
BFDE393C1BC0CEDF003F72E8 /* Game.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDE39391BC0CEDF003F72E8 /* Game.swift */; };
|
||||
BFE704F51CEA426E0058BAC8 /* Pods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 22506DA00971C4300AF90A35 /* Pods.framework */; };
|
||||
BFEC732D1AAECC4A00650035 /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFEC732C1AAECC4A00650035 /* Roxas.framework */; };
|
||||
@ -108,7 +107,6 @@
|
||||
BF27CC861BC9E3C600A20D89 /* Delta.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = Delta.entitlements; sourceTree = "<group>"; };
|
||||
BF27CC8A1BC9FE4D00A20D89 /* Pods.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Pods.framework; path = "Pods/../build/Debug-appletvos/Pods.framework"; sourceTree = "<group>"; };
|
||||
BF27CC941BCB7B7A00A20D89 /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS9.0.sdk/System/Library/Frameworks/GameController.framework; sourceTree = DEVELOPER_DIR; };
|
||||
BF27CC961BCC890700A20D89 /* GamesCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GamesCollectionViewController.swift; sourceTree = "<group>"; };
|
||||
BF2B98E51C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SaveStatesCollectionHeaderView.swift; path = "Pause Menu/Save States/SaveStatesCollectionHeaderView.swift"; sourceTree = "<group>"; };
|
||||
BF31878A1D489AAA00BD020D /* CheatValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CheatValidator.swift; path = "Pause Menu/Cheats/CheatValidator.swift"; sourceTree = "<group>"; };
|
||||
BF34FA061CF0F510006624C7 /* EditCheatViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EditCheatViewController.swift; path = "Pause Menu/Cheats/EditCheatViewController.swift"; sourceTree = "<group>"; };
|
||||
@ -138,13 +136,13 @@
|
||||
BFA2315B1CED10BE0011E35A /* Action.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Action.swift; path = Components/Action.swift; sourceTree = "<group>"; };
|
||||
BFAA1FEC1B8AA4FA00495943 /* Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
|
||||
BFAA1FF31B8AD7F900495943 /* ControllersSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControllersSettingsViewController.swift; sourceTree = "<group>"; };
|
||||
BFB141171BE46934004FBF46 /* GameCollectionViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameCollectionViewDataSource.swift; path = "Collection View/GameCollectionViewDataSource.swift"; sourceTree = "<group>"; };
|
||||
BFBAA8691D5A483900A29C1B /* DatabaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseManager.swift; sourceTree = "<group>"; };
|
||||
BFC134E01AAD82460087AD7B /* SNESDeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SNESDeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BFC273171BE6152200D22B05 /* GameCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameCollection.swift; sourceTree = "<group>"; };
|
||||
BFC9B7381CEFCD34008629BB /* CheatsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CheatsViewController.swift; path = "Pause Menu/Cheats/CheatsViewController.swift"; sourceTree = "<group>"; };
|
||||
BFCEA67D1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewControllerContextTransitioning+Conveniences.swift"; sourceTree = "<group>"; };
|
||||
BFDB28441BC9DA7B001D0C83 /* GamePickerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GamePickerController.swift; sourceTree = "<group>"; };
|
||||
BFDD04F01D5E2C27002D450E /* GameCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameCollectionViewController.swift; sourceTree = "<group>"; };
|
||||
BFDE39391BC0CEDF003F72E8 /* Game.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Game.swift; sourceTree = "<group>"; };
|
||||
BFEC732C1AAECC4A00650035 /* Roxas.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Roxas.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BFFA71D71AAC406100EE9DD1 /* Delta.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Delta.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@ -253,7 +251,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF107EC31BF413F000E0C32C /* GamesViewController.swift */,
|
||||
BF27CC961BCC890700A20D89 /* GamesCollectionViewController.swift */,
|
||||
BFDD04F01D5E2C27002D450E /* GameCollectionViewController.swift */,
|
||||
BFFC461A1D59820F00AF2CC6 /* Segues */,
|
||||
);
|
||||
path = "Game Selection";
|
||||
@ -299,7 +297,6 @@
|
||||
children = (
|
||||
BF7AE81A1C2E984300B1B5BC /* GridCollectionViewCell.swift */,
|
||||
BF7AE81D1C2E984300B1B5BC /* GridCollectionViewLayout.swift */,
|
||||
BFB141171BE46934004FBF46 /* GameCollectionViewDataSource.swift */,
|
||||
);
|
||||
name = "Collection View";
|
||||
sourceTree = "<group>";
|
||||
@ -581,13 +578,12 @@
|
||||
BFA0D1271D3AE1F600565894 /* GameViewController.swift in Sources */,
|
||||
BF13A7581D5D2FD9000BB055 /* EmulatorCore+Cheats.swift in Sources */,
|
||||
BF31878B1D489AAA00BD020D /* CheatValidator.swift in Sources */,
|
||||
BFB141181BE46934004FBF46 /* GameCollectionViewDataSource.swift in Sources */,
|
||||
BFFC46201D59823500AF2CC6 /* InitialGamesStoryboardSegue.swift in Sources */,
|
||||
BFA2315C1CED10BE0011E35A /* Action.swift in Sources */,
|
||||
BFAA1FF41B8AD7F900495943 /* ControllersSettingsViewController.swift in Sources */,
|
||||
BF353FF91C5D870B00C1184C /* PauseItem.swift in Sources */,
|
||||
BF27CC971BCC890700A20D89 /* GamesCollectionViewController.swift in Sources */,
|
||||
BFBAA86A1D5A483900A29C1B /* DatabaseManager.swift in Sources */,
|
||||
BFDD04F11D5E2C27002D450E /* GameCollectionViewController.swift in Sources */,
|
||||
BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */,
|
||||
BF7AE81E1C2E984300B1B5BC /* GridCollectionViewCell.swift in Sources */,
|
||||
BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */,
|
||||
@ -601,7 +597,6 @@
|
||||
BF762EAB1BC1B076002C8866 /* NSManagedObject+Conveniences.swift in Sources */,
|
||||
BFC9B7391CEFCD34008629BB /* CheatsViewController.swift in Sources */,
|
||||
BF353FFF1C5DA3C500C1184C /* PausePresentationController.swift in Sources */,
|
||||
BF02BD001D361BD1000892F2 /* NSFetchedResultsController+Conveniences.m in Sources */,
|
||||
BFFC464C1D5998D600AF2CC6 /* CheatTableViewCell.swift in Sources */,
|
||||
BF7AE80A1C2E8C7600B1B5BC /* UIColor+Delta.swift in Sources */,
|
||||
BF090CF41B490D8300DCAB45 /* UIDevice+Vibration.m in Sources */,
|
||||
@ -613,6 +608,7 @@
|
||||
BF107EC41BF413F000E0C32C /* GamesViewController.swift in Sources */,
|
||||
BF172AEB1C68986300C26774 /* NSManagedObjectContext+Conveniences.swift in Sources */,
|
||||
BF13A7561D5D29B0000BB055 /* PreviewGameViewController.swift in Sources */,
|
||||
BFDD04EF1D5E27DB002D450E /* NSFetchedResultsController+Conveniences.m in Sources */,
|
||||
BFCEA67E1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift in Sources */,
|
||||
BFFC461E1D59823500AF2CC6 /* GamesPresentationController.swift in Sources */,
|
||||
BF5E7F441B9A650B00AE44F8 /* SettingsViewController.swift in Sources */,
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11185.3" systemVersion="15G31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="yhz-fF-D91">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11191" systemVersion="15G31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="yhz-fF-D91">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11151.4"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11156"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@ -20,7 +20,7 @@
|
||||
<subviews>
|
||||
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="tmn-gd-5UN">
|
||||
<connections>
|
||||
<segue destination="tpK-ou-yEA" kind="embed" id="cjU-nW-cHY"/>
|
||||
<segue destination="tpK-ou-yEA" kind="embed" identifier="embedPageViewController" id="cjU-nW-cHY"/>
|
||||
</connections>
|
||||
</containerView>
|
||||
</subviews>
|
||||
@ -49,10 +49,10 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1036" y="1002"/>
|
||||
</scene>
|
||||
<!--Games Collection View Controller-->
|
||||
<!--Game Collection View Controller-->
|
||||
<scene sceneID="qNA-NP-TiF">
|
||||
<objects>
|
||||
<collectionViewController storyboardIdentifier="gamesCollectionViewController" clearsSelectionOnViewWillAppear="NO" id="kqu-75-owz" customClass="GamesCollectionViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<collectionViewController storyboardIdentifier="gameCollectionViewController" clearsSelectionOnViewWillAppear="NO" id="kqu-75-owz" customClass="GameCollectionViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" id="OIq-Z8-kxO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
@ -64,16 +64,13 @@
|
||||
<inset key="sectionInset" minX="0.0" minY="20" maxX="0.0" maxY="20"/>
|
||||
</collectionViewFlowLayout>
|
||||
<cells>
|
||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="GameCell" id="ioT-sh-j8y" customClass="GridCollectionViewCell" customModule="Delta" customModuleProvider="target">
|
||||
<frame key="frameInset" minY="20" width="100" height="100"/>
|
||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="ioT-sh-j8y" customClass="GridCollectionViewCell" customModule="Delta" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="20" width="100" height="100"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||
<rect key="frame" x="0.0" y="0.0" width="100" height="100"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</view>
|
||||
<connections>
|
||||
<segue destination="X2o-q6-XD5" kind="unwind" unwindAction="unwindFromGamesViewControllerWith:" id="Cxx-73-8KV"/>
|
||||
</connections>
|
||||
</collectionViewCell>
|
||||
</cells>
|
||||
<connections>
|
||||
@ -81,6 +78,9 @@
|
||||
<outlet property="delegate" destination="kqu-75-owz" id="B0G-y1-UrU"/>
|
||||
</connections>
|
||||
</collectionView>
|
||||
<connections>
|
||||
<segue destination="X2o-q6-XD5" kind="unwind" identifier="unwindFromGames" unwindAction="unwindFromGamesViewControllerWith:" id="k8C-Xn-maU"/>
|
||||
</connections>
|
||||
</collectionViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="bW1-t8-idm" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
<exit id="X2o-q6-XD5" userLabel="Exit" sceneMemberID="exit"/>
|
||||
|
||||
152
Delta/Game Selection/GameCollectionViewController.swift
Normal file
152
Delta/Game Selection/GameCollectionViewController.swift
Normal file
@ -0,0 +1,152 @@
|
||||
//
|
||||
// GameCollectionViewController.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 8/12/16.
|
||||
// Copyright © 2016 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
import Roxas
|
||||
|
||||
class GameCollectionViewController: UICollectionViewController
|
||||
{
|
||||
var gameCollection: GameCollection! {
|
||||
didSet {
|
||||
self.title = self.gameCollection.shortName
|
||||
self.updateDataSource()
|
||||
}
|
||||
}
|
||||
|
||||
var theme: GamesViewController.Theme = .light {
|
||||
didSet {
|
||||
self.collectionView?.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
private var dataSource: RSTFetchedResultsCollectionViewDataSource<Game>!
|
||||
private let prototypeCell = GridCollectionViewCell()
|
||||
}
|
||||
|
||||
//MARK: - UIViewController -
|
||||
/// UIViewController
|
||||
extension GameCollectionViewController
|
||||
{
|
||||
override func viewDidLoad()
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.collectionView?.dataSource = self.dataSource
|
||||
self.collectionView?.delegate = self
|
||||
|
||||
let layout = self.collectionViewLayout as! GridCollectionViewLayout
|
||||
layout.itemWidth = 90
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool)
|
||||
{
|
||||
self.dataSource.fetchedResultsController.performFetchIfNeeded()
|
||||
|
||||
super.viewWillAppear(animated)
|
||||
}
|
||||
|
||||
override func didReceiveMemoryWarning()
|
||||
{
|
||||
super.didReceiveMemoryWarning()
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Segues -
|
||||
/// Segues
|
||||
extension GameCollectionViewController
|
||||
{
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?)
|
||||
{
|
||||
guard let identifier = segue.identifier, identifier == "unwindFromGames" else { return }
|
||||
|
||||
let destinationViewController = segue.destination as! GameViewController
|
||||
let cell = sender as! UICollectionViewCell
|
||||
|
||||
let indexPath = self.collectionView?.indexPath(for: cell)
|
||||
let game = self.dataSource.fetchedResultsController.object(at: indexPath!)
|
||||
|
||||
destinationViewController.game = game
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Configure Cells -
|
||||
/// Configure Cells
|
||||
private extension GameCollectionViewController
|
||||
{
|
||||
func updateDataSource()
|
||||
{
|
||||
let fetchRequest = Game.rst_fetchRequest() as! NSFetchRequest<Game>
|
||||
fetchRequest.predicate = NSPredicate(format: "ANY %K == %@", #keyPath(Game.gameCollections), self.gameCollection)
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(Game.name), ascending: true)]
|
||||
|
||||
self.dataSource = RSTFetchedResultsCollectionViewDataSource(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
|
||||
self.dataSource.cellIdentifierHandler = { _ in RSTGenericCellIdentifier }
|
||||
self.dataSource.cellConfigurationHandler = { [unowned self] (cell, indexPath) in
|
||||
self.configure(cell as! GridCollectionViewCell, for: indexPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Configure Cells -
|
||||
/// Configure Cells
|
||||
private extension GameCollectionViewController
|
||||
{
|
||||
func configure(_ cell: GridCollectionViewCell, for indexPath: IndexPath)
|
||||
{
|
||||
let game = self.dataSource.fetchedResultsController.object(at: indexPath)
|
||||
|
||||
cell.maximumImageSize = CGSize(width: 90, height: 90)
|
||||
cell.textLabel.text = game.name
|
||||
cell.imageView.image = UIImage(named: "BoxArt")
|
||||
|
||||
switch self.theme
|
||||
{
|
||||
case .light:
|
||||
cell.textLabel.textColor = UIColor.darkText
|
||||
cell.isTextLabelVibrancyEnabled = false
|
||||
cell.isImageViewVibrancyEnabled = false
|
||||
|
||||
case .dark:
|
||||
cell.textLabel.textColor = UIColor.white
|
||||
cell.isTextLabelVibrancyEnabled = true
|
||||
cell.isImageViewVibrancyEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - UICollectionViewDelegate -
|
||||
/// UICollectionViewDelegate
|
||||
extension GameCollectionViewController
|
||||
{
|
||||
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
|
||||
{
|
||||
let cell = collectionView.cellForItem(at: indexPath)
|
||||
self.performSegue(withIdentifier: "unwindFromGames", sender: cell)
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - UICollectionViewDelegateFlowLayout -
|
||||
/// UICollectionViewDelegateFlowLayout
|
||||
extension GameCollectionViewController: UICollectionViewDelegateFlowLayout
|
||||
{
|
||||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
|
||||
{
|
||||
let collectionViewLayout = collectionView.collectionViewLayout as! GridCollectionViewLayout
|
||||
|
||||
let widthConstraint = self.prototypeCell.contentView.widthAnchor.constraint(equalToConstant: collectionViewLayout.itemWidth)
|
||||
widthConstraint.isActive = true
|
||||
defer { widthConstraint.isActive = false }
|
||||
|
||||
self.configure(self.prototypeCell, for: indexPath)
|
||||
|
||||
let size = self.prototypeCell.contentView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
|
||||
return size
|
||||
}
|
||||
}
|
||||
@ -1,107 +0,0 @@
|
||||
//
|
||||
// GamesCollectionViewController.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 10/12/15.
|
||||
// Copyright © 2015 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
|
||||
import DeltaCore
|
||||
|
||||
class GamesCollectionViewController: UICollectionViewController
|
||||
{
|
||||
var theme: GamesViewController.Theme = .light {
|
||||
didSet {
|
||||
self.collectionView?.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
weak var segueHandler: UIViewController?
|
||||
|
||||
var gameCollection: GameCollection! {
|
||||
didSet
|
||||
{
|
||||
self.dataSource.supportedGameCollectionIdentifiers = [self.gameCollection.identifier]
|
||||
self.title = self.gameCollection.shortName
|
||||
}
|
||||
}
|
||||
|
||||
let dataSource = GameCollectionViewDataSource()
|
||||
|
||||
required init?(coder aDecoder: NSCoder)
|
||||
{
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
self.dataSource.fetchedResultsController.delegate = self
|
||||
self.dataSource.cellConfigurationHandler = { [unowned self] (cell, game) in
|
||||
self.configureCell(cell, game: game)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLoad()
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.collectionView?.dataSource = self.dataSource
|
||||
self.collectionView?.delegate = self.dataSource
|
||||
|
||||
if let layout = self.collectionViewLayout as? GridCollectionViewLayout
|
||||
{
|
||||
layout.itemWidth = 90
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool)
|
||||
{
|
||||
self.dataSource.update()
|
||||
|
||||
super.viewWillAppear(animated)
|
||||
}
|
||||
|
||||
override func didReceiveMemoryWarning()
|
||||
{
|
||||
super.didReceiveMemoryWarning()
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
|
||||
// MARK: - Navigation -
|
||||
|
||||
// In a storyboard-based application, you will often want to do a little preparation before navigation
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?)
|
||||
{
|
||||
self.segueHandler?.prepare(for: segue, sender: sender)
|
||||
}
|
||||
|
||||
// MARK: - Collection View -
|
||||
|
||||
private func configureCell(_ cell: GridCollectionViewCell, game: Game)
|
||||
{
|
||||
cell.maximumImageSize = CGSize(width: 90, height: 90)
|
||||
cell.textLabel.text = game.name
|
||||
cell.imageView.image = UIImage(named: "BoxArt")
|
||||
|
||||
switch self.theme
|
||||
{
|
||||
case .light:
|
||||
cell.textLabel.textColor = UIColor.darkText
|
||||
cell.isTextLabelVibrancyEnabled = false
|
||||
cell.isImageViewVibrancyEnabled = false
|
||||
|
||||
case .dark:
|
||||
cell.textLabel.textColor = UIColor.white
|
||||
cell.isTextLabelVibrancyEnabled = true
|
||||
cell.isImageViewVibrancyEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension GamesCollectionViewController: NSFetchedResultsControllerDelegate
|
||||
{
|
||||
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
|
||||
{
|
||||
self.collectionView?.reloadData()
|
||||
}
|
||||
}
|
||||
@ -43,37 +43,37 @@ class GamesViewController: UIViewController
|
||||
required init?(coder aDecoder: NSCoder)
|
||||
{
|
||||
let fetchRequest = GameCollection.rst_fetchRequest()
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: GameCollection.Attributes.index.rawValue, ascending: true)]
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(GameCollection.index), ascending: true)]
|
||||
|
||||
self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext, sectionNameKeyPath: nil, cacheName: nil)
|
||||
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
self.fetchedResultsController.delegate = self
|
||||
|
||||
self.automaticallyAdjustsScrollViewInsets = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//MARK: - UIViewController -
|
||||
/// UIViewController
|
||||
extension GamesViewController
|
||||
{
|
||||
override func viewDidLoad()
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.automaticallyAdjustsScrollViewInsets = false
|
||||
|
||||
self.backgroundView = RSTBackgroundView(frame: self.view.bounds)
|
||||
self.backgroundView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
self.backgroundView.textLabel.text = NSLocalizedString("No Games", comment: "")
|
||||
self.backgroundView.detailTextLabel.text = NSLocalizedString("You can import games by pressing the + button in the top right.", comment: "")
|
||||
self.view.insertSubview(self.backgroundView, at: 0)
|
||||
|
||||
self.pageViewController = self.childViewControllers.first as? UIPageViewController
|
||||
self.pageViewController.dataSource = self
|
||||
self.pageViewController.delegate = self
|
||||
self.pageViewController.view.isHidden = true
|
||||
|
||||
self.pageControl = UIPageControl()
|
||||
self.pageControl.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.pageControl.hidesForSinglePage = false
|
||||
self.pageControl.numberOfPages = 3
|
||||
self.pageControl.currentPageIndicatorTintColor = UIColor.purple
|
||||
self.pageControl.currentPageIndicatorTintColor = UIColor.deltaPurpleColor()
|
||||
self.pageControl.pageIndicatorTintColor = UIColor.lightGray
|
||||
self.navigationController?.toolbar.addSubview(self.pageControl)
|
||||
|
||||
@ -85,17 +85,8 @@ class GamesViewController: UIViewController
|
||||
{
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
if self.fetchedResultsController.fetchedObjects == nil
|
||||
if self.fetchedResultsController.performFetchIfNeeded()
|
||||
{
|
||||
do
|
||||
{
|
||||
try self.fetchedResultsController.performFetch()
|
||||
}
|
||||
catch let error as NSError
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
|
||||
self.updateSections()
|
||||
}
|
||||
}
|
||||
@ -104,7 +95,7 @@ class GamesViewController: UIViewController
|
||||
{
|
||||
super.viewDidLayoutSubviews()
|
||||
|
||||
if let viewControllers = self.pageViewController.viewControllers as? [GamesCollectionViewController]
|
||||
if let viewControllers = self.pageViewController.viewControllers as? [GameCollectionViewController]
|
||||
{
|
||||
for viewController in viewControllers
|
||||
{
|
||||
@ -113,7 +104,8 @@ class GamesViewController: UIViewController
|
||||
}
|
||||
}
|
||||
|
||||
override func didReceiveMemoryWarning() {
|
||||
override func didReceiveMemoryWarning()
|
||||
{
|
||||
super.didReceiveMemoryWarning()
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
@ -126,14 +118,12 @@ extension GamesViewController
|
||||
// In a storyboard-based application, you will often want to do a little preparation before navigation
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?)
|
||||
{
|
||||
guard let sourceViewController = segue.source as? GamesCollectionViewController else { return }
|
||||
guard let destinationViewController = segue.destination as? GameViewController else { return }
|
||||
guard let cell = sender as? UICollectionViewCell else { return }
|
||||
guard let identifier = segue.identifier, identifier == "embedPageViewController" else { return }
|
||||
|
||||
let indexPath = sourceViewController.collectionView?.indexPath(for: cell)
|
||||
let game = sourceViewController.dataSource.fetchedResultsController.object(at: indexPath!) as! Game
|
||||
|
||||
destinationViewController.game = game
|
||||
self.pageViewController = segue.destination as! UIPageViewController
|
||||
self.pageViewController.dataSource = self
|
||||
self.pageViewController.delegate = self
|
||||
self.pageViewController.view.isHidden = true
|
||||
}
|
||||
|
||||
@IBAction private func unwindFromSettingsViewController(_ segue: UIStoryboardSegue)
|
||||
@ -160,9 +150,12 @@ private extension GamesViewController
|
||||
self.navigationController?.toolbar.barStyle = .blackTranslucent
|
||||
}
|
||||
|
||||
if let collectionViewController = self.pageViewController.viewControllers?.first as? UICollectionViewController
|
||||
if let viewControllers = self.pageViewController.viewControllers as? [GameCollectionViewController]
|
||||
{
|
||||
collectionViewController.collectionView?.reloadData()
|
||||
for collectionViewController in viewControllers
|
||||
{
|
||||
collectionViewController.theme = self.theme
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -170,7 +163,7 @@ private extension GamesViewController
|
||||
// MARK: - Helper Methods -
|
||||
private extension GamesViewController
|
||||
{
|
||||
func viewControllerForIndex(_ index: Int) -> GamesCollectionViewController?
|
||||
func viewControllerForIndex(_ index: Int) -> GameCollectionViewController?
|
||||
{
|
||||
guard let pages = self.fetchedResultsController.sections?.first?.numberOfObjects, pages > 0 else { return nil }
|
||||
|
||||
@ -185,10 +178,8 @@ private extension GamesViewController
|
||||
|
||||
let indexPath = IndexPath(row: safeIndex, section: 0)
|
||||
|
||||
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "gamesCollectionViewController") as! GamesCollectionViewController
|
||||
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "gameCollectionViewController") as! GameCollectionViewController
|
||||
viewController.gameCollection = self.fetchedResultsController.object(at: indexPath) as! GameCollection
|
||||
viewController.collectionView?.contentInset.top = self.topLayoutGuide.length
|
||||
viewController.segueHandler = self
|
||||
viewController.theme = self.theme
|
||||
|
||||
return viewController
|
||||
@ -201,7 +192,7 @@ private extension GamesViewController
|
||||
|
||||
var resetPageViewController = false
|
||||
|
||||
if let viewController = pageViewController.viewControllers?.first as? GamesCollectionViewController, let gameCollection = viewController.gameCollection
|
||||
if let viewController = pageViewController.viewControllers?.first as? GameCollectionViewController, let gameCollection = viewController.gameCollection
|
||||
{
|
||||
if let index = self.fetchedResultsController.fetchedObjects?.index(where: { $0 as! GameCollection == gameCollection })
|
||||
{
|
||||
@ -252,7 +243,7 @@ private extension GamesViewController
|
||||
}
|
||||
|
||||
//MARK: - Importing -
|
||||
// Importing
|
||||
/// Importing
|
||||
extension GamesViewController: GamePickerControllerDelegate
|
||||
{
|
||||
@IBAction private func importFiles()
|
||||
@ -270,7 +261,7 @@ extension GamesViewController: GamePickerControllerDelegate
|
||||
}
|
||||
|
||||
//MARK: - UIPageViewController -
|
||||
// UIPageViewController
|
||||
/// UIPageViewController
|
||||
extension GamesViewController: UIPageViewControllerDataSource, UIPageViewControllerDelegate
|
||||
{
|
||||
//MARK: - UIPageViewControllerDataSource
|
||||
@ -287,9 +278,19 @@ extension GamesViewController: UIPageViewControllerDataSource, UIPageViewControl
|
||||
}
|
||||
|
||||
//MARK: - UIPageViewControllerDelegate
|
||||
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController])
|
||||
{
|
||||
guard let viewControllers = pendingViewControllers as? [GameCollectionViewController] else { return }
|
||||
|
||||
for viewController in viewControllers
|
||||
{
|
||||
viewController.collectionView?.contentInset.top = self.topLayoutGuide.length
|
||||
}
|
||||
}
|
||||
|
||||
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool)
|
||||
{
|
||||
if let viewController = pageViewController.viewControllers?.first as? GamesCollectionViewController, let gameCollection = viewController.gameCollection
|
||||
if let viewController = pageViewController.viewControllers?.first as? GameCollectionViewController, let gameCollection = viewController.gameCollection
|
||||
{
|
||||
let index = self.fetchedResultsController.fetchedObjects?.index(where: { $0 as! GameCollection == gameCollection }) ?? 0
|
||||
self.pageControl.currentPage = index
|
||||
@ -300,7 +301,7 @@ extension GamesViewController: UIPageViewControllerDataSource, UIPageViewControl
|
||||
}
|
||||
|
||||
//MARK: - NSFetchedResultsControllerDelegate -
|
||||
// NSFetchedResultsControllerDelegate
|
||||
/// NSFetchedResultsControllerDelegate
|
||||
extension GamesViewController: NSFetchedResultsControllerDelegate
|
||||
{
|
||||
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
|
||||
|
||||
2
External/Roxas
vendored
2
External/Roxas
vendored
@ -1 +1 @@
|
||||
Subproject commit f9aa36cc59b228bf8182044da017f39b79b7475a
|
||||
Subproject commit f0661f78b095212ed2f564a4bbf1c1f6c9c50384
|
||||
Loading…
Reference in New Issue
Block a user