Update iOS Sample, migrate to Swift5. #1105
@@ -2,5 +2,5 @@ use_frameworks!
|
|||||||
|
|
||||||
target 'YogaKitSample' do
|
target 'YogaKitSample' do
|
||||||
pod 'YogaKit', :path => '../../YogaKit.podspec'
|
pod 'YogaKit', :path => '../../YogaKit.podspec'
|
||||||
pod 'IGListKit', '~> 2.1.0'
|
pod 'IGListKit', '~> 4.0.0'
|
||||||
end
|
end
|
||||||
|
@@ -1,26 +1,31 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- IGListKit (2.1.0):
|
- IGListDiffKit (4.0.0)
|
||||||
- IGListKit/Default (= 2.1.0)
|
- IGListKit (4.0.0):
|
||||||
- IGListKit/Default (2.1.0):
|
- IGListDiffKit (= 4.0.0)
|
||||||
- IGListKit/Diffing
|
- Yoga (1.14.0)
|
||||||
- IGListKit/Diffing (2.1.0)
|
- YogaKit (1.18.1):
|
||||||
- Yoga (1.7.0)
|
- Yoga (~> 1.14)
|
||||||
- YogaKit (1.7.0):
|
|
||||||
- Yoga (~> 1.7)
|
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- IGListKit (~> 2.1.0)
|
- IGListKit (~> 4.0.0)
|
||||||
- YogaKit (from `../../YogaKit.podspec`)
|
- YogaKit (from `../../YogaKit.podspec`)
|
||||||
|
|
||||||
|
SPEC REPOS:
|
||||||
|
trunk:
|
||||||
|
- IGListDiffKit
|
||||||
|
- IGListKit
|
||||||
|
- Yoga
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
YogaKit:
|
YogaKit:
|
||||||
:path: ../../YogaKit.podspec
|
:path: "../../YogaKit.podspec"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
IGListKit: b826c68ef7a4ae1626c09d4d3e1ea7a169e6c36e
|
IGListDiffKit: 665d6cf43ce726e676013db9c7d6c4294259b6b2
|
||||||
Yoga: 2ed1d7accfef3610a67f58c0cf101a0662137f2c
|
IGListKit: fd5a5d21935298f5849fa49d426843cff97b77c7
|
||||||
YogaKit: 31576530e8fcae3175469719ec3212397403330b
|
Yoga: cff67a400f6b74dc38eb0bad4f156673d9aa980c
|
||||||
|
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
|
||||||
|
|
||||||
PODFILE CHECKSUM: 216f8e7127767709e0e43f3711208d238fa5c404
|
PODFILE CHECKSUM: 017f31da69556eca75fc988a3ebd7d355d63bb05
|
||||||
|
|
||||||
COCOAPODS: 1.1.1
|
COCOAPODS: 1.11.0
|
||||||
|
@@ -1,10 +1,3 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// !$*UTF8*$!
|
// !$*UTF8*$!
|
||||||
{
|
{
|
||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
@@ -178,7 +171,6 @@
|
|||||||
13687D401DF8748300E7C260 /* Frameworks */,
|
13687D401DF8748300E7C260 /* Frameworks */,
|
||||||
13687D411DF8748300E7C260 /* Resources */,
|
13687D411DF8748300E7C260 /* Resources */,
|
||||||
FA2FB9DD6471BDD3FBCE503B /* [CP] Embed Pods Frameworks */,
|
FA2FB9DD6471BDD3FBCE503B /* [CP] Embed Pods Frameworks */,
|
||||||
6E01EB987F1564F3D71EBE5A /* [CP] Copy Pods Resources */,
|
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -233,6 +225,7 @@
|
|||||||
developmentRegion = English;
|
developmentRegion = English;
|
||||||
hasScannedForEncodings = 0;
|
hasScannedForEncodings = 0;
|
||||||
knownRegions = (
|
knownRegions = (
|
||||||
|
English,
|
||||||
en,
|
en,
|
||||||
Base,
|
Base,
|
||||||
);
|
);
|
||||||
@@ -272,28 +265,16 @@
|
|||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
inputPaths = (
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
);
|
);
|
||||||
name = "[CP] Check Pods Manifest.lock";
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
outputPaths = (
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-YogaKitSample-checkManifestLockResult.txt",
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
showEnvVarsInLog = 0;
|
|
||||||
};
|
|
||||||
6E01EB987F1564F3D71EBE5A /* [CP] Copy Pods Resources */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "[CP] Copy Pods Resources";
|
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-YogaKitSample/Pods-YogaKitSample-resources.sh\"\n";
|
|
||||||
showEnvVarsInLog = 0;
|
showEnvVarsInLog = 0;
|
||||||
};
|
};
|
||||||
FA2FB9DD6471BDD3FBCE503B /* [CP] Embed Pods Frameworks */ = {
|
FA2FB9DD6471BDD3FBCE503B /* [CP] Embed Pods Frameworks */ = {
|
||||||
@@ -302,13 +283,22 @@
|
|||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
inputPaths = (
|
inputPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-YogaKitSample/Pods-YogaKitSample-frameworks.sh",
|
||||||
|
"${BUILT_PRODUCTS_DIR}/IGListDiffKit/IGListDiffKit.framework",
|
||||||
|
"${BUILT_PRODUCTS_DIR}/IGListKit/IGListKit.framework",
|
||||||
|
"${BUILT_PRODUCTS_DIR}/Yoga/yoga.framework",
|
||||||
|
"${BUILT_PRODUCTS_DIR}/YogaKit/YogaKit.framework",
|
||||||
);
|
);
|
||||||
name = "[CP] Embed Pods Frameworks";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
outputPaths = (
|
outputPaths = (
|
||||||
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IGListDiffKit.framework",
|
||||||
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IGListKit.framework",
|
||||||
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/yoga.framework",
|
||||||
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YogaKit.framework",
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-YogaKitSample/Pods-YogaKitSample-frameworks.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-YogaKitSample/Pods-YogaKitSample-frameworks.sh\"\n";
|
||||||
showEnvVarsInLog = 0;
|
showEnvVarsInLog = 0;
|
||||||
};
|
};
|
||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
@@ -441,12 +431,13 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
INFOPLIST_FILE = YogaKitSample/Info.plist;
|
INFOPLIST_FILE = YogaKitSample/Info.plist;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.YogaKitSample;
|
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.YogaKitSample;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_INSTALL_OBJC_HEADER = NO;
|
SWIFT_INSTALL_OBJC_HEADER = NO;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 3.0;
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
@@ -457,11 +448,12 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
INFOPLIST_FILE = YogaKitSample/Info.plist;
|
INFOPLIST_FILE = YogaKitSample/Info.plist;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.YogaKitSample;
|
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.YogaKitSample;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_INSTALL_OBJC_HEADER = NO;
|
SWIFT_INSTALL_OBJC_HEADER = NO;
|
||||||
SWIFT_VERSION = 3.0;
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@@ -18,12 +18,12 @@ private final class ExampleModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ExampleModel: IGListDiffable {
|
extension ExampleModel: ListDiffable {
|
||||||
fileprivate func diffIdentifier() -> NSObjectProtocol {
|
fileprivate func diffIdentifier() -> NSObjectProtocol {
|
||||||
return title as NSString
|
return title as NSString
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func isEqual(toDiffableObject object: IGListDiffable?) -> Bool {
|
fileprivate func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
|
||||||
guard let otherObj = object as? ExampleModel else { return false }
|
guard let otherObj = object as? ExampleModel else { return false }
|
||||||
|
|
||||||
return (title == otherObj.title) &&
|
return (title == otherObj.title) &&
|
||||||
@@ -31,12 +31,16 @@ extension ExampleModel: IGListDiffable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class ExamplesViewController: UIViewController, IGListAdapterDataSource, IGListSingleSectionControllerDelegate {
|
final class ExamplesViewController: UIViewController, ListAdapterDataSource, ListSingleSectionControllerDelegate {
|
||||||
private lazy var adapter: IGListAdapter = {
|
|
||||||
return IGListAdapter(updater: IGListAdapterUpdater(), viewController: self, workingRangeSize: 0)
|
|
||||||
}()
|
|
||||||
private let collectionView = IGListCollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
|
|
||||||
|
|
||||||
|
private lazy var adapter: ListAdapter = {
|
||||||
|
return ListAdapter(updater: ListAdapterUpdater(), viewController: self, workingRangeSize: 0)
|
||||||
|
}()
|
||||||
|
private lazy var collectionView: UICollectionView = {
|
||||||
|
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
|
||||||
|
collectionView.backgroundColor = UIColor.systemBackground
|
||||||
|
return collectionView
|
||||||
|
}()
|
||||||
|
|
||||||
// Update this to array to create more examples.
|
// Update this to array to create more examples.
|
||||||
private let models: [ExampleModel] = [ExampleModel(title: "Basic Layout", controllerClass: BasicViewController.self),
|
private let models: [ExampleModel] = [ExampleModel(title: "Basic Layout", controllerClass: BasicViewController.self),
|
||||||
@@ -59,16 +63,16 @@ final class ExamplesViewController: UIViewController, IGListAdapterDataSource, I
|
|||||||
|
|
||||||
//MARK: IGListAdapterDataSource
|
//MARK: IGListAdapterDataSource
|
||||||
|
|
||||||
func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] {
|
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
|
||||||
return models as [IGListDiffable]
|
return models as [ListDiffable]
|
||||||
}
|
}
|
||||||
|
|
||||||
func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController {
|
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
|
||||||
let sizeBlock: IGListSingleSectionCellSizeBlock = { (model, context) in
|
let sizeBlock: ListSingleSectionCellSizeBlock = { (model, context) in
|
||||||
return CGSize(width: (context?.containerSize.width)!, height: 75.0)
|
return CGSize(width: (context?.containerSize.width)!, height: 75.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
let configureBlock: IGListSingleSectionCellConfigureBlock = { (model, cell) in
|
let configureBlock: ListSingleSectionCellConfigureBlock = { (model, cell) in
|
||||||
guard let m = model as? ExampleModel, let c = cell as? SingleLabelCollectionCell else {
|
guard let m = model as? ExampleModel, let c = cell as? SingleLabelCollectionCell else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -76,18 +80,17 @@ final class ExamplesViewController: UIViewController, IGListAdapterDataSource, I
|
|||||||
c.label.text = m.title
|
c.label.text = m.title
|
||||||
}
|
}
|
||||||
|
|
||||||
let sectionController = IGListSingleSectionController(cellClass: SingleLabelCollectionCell.self,
|
let sectionController = ListSingleSectionController(cellClass: SingleLabelCollectionCell.self,
|
||||||
configureBlock: configureBlock,
|
configureBlock: configureBlock,
|
||||||
sizeBlock: sizeBlock)
|
sizeBlock: sizeBlock)
|
||||||
sectionController.selectionDelegate = self
|
sectionController.selectionDelegate = self
|
||||||
return sectionController
|
return sectionController
|
||||||
}
|
}
|
||||||
|
|
||||||
func emptyView(for listAdapter: IGListAdapter) -> UIView? { return nil }
|
func emptyView(for listAdapter: ListAdapter) -> UIView? { return nil }
|
||||||
|
|
||||||
//MARK: IGListSingleSectionControllerDelegate
|
//MARK: IGListSingleSectionControllerDelegate
|
||||||
|
func didSelect(_ sectionController: ListSingleSectionController, with object: Any) {
|
||||||
func didSelect(_ sectionController: IGListSingleSectionController) {
|
|
||||||
let section = adapter.section(for: sectionController)
|
let section = adapter.section(for: sectionController)
|
||||||
let model = models[section]
|
let model = models[section]
|
||||||
|
|
||||||
|
@@ -10,11 +10,11 @@ import YogaKit
|
|||||||
|
|
||||||
final class BasicViewController: UIViewController {
|
final class BasicViewController: UIViewController {
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
let containerSize = self.view.bounds.size
|
let containerSize = self.view.bounds.size
|
||||||
|
|
||||||
let root = self.view!
|
view.backgroundColor = UIColor.systemBackground
|
||||||
root.backgroundColor = .white
|
view.configureLayout { (layout) in
|
||||||
root.configureLayout { (layout) in
|
|
||||||
layout.isEnabled = true
|
layout.isEnabled = true
|
||||||
layout.width = YGValue(containerSize.width)
|
layout.width = YGValue(containerSize.width)
|
||||||
layout.height = YGValue(containerSize.height)
|
layout.height = YGValue(containerSize.height)
|
||||||
@@ -30,24 +30,29 @@ final class BasicViewController: UIViewController {
|
|||||||
layout.height = 10
|
layout.height = 10
|
||||||
layout.marginBottom = 25
|
layout.marginBottom = 25
|
||||||
}
|
}
|
||||||
root.addSubview(child1)
|
view.addSubview(child1)
|
||||||
|
|
||||||
let child2 = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
|
let child2 = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
|
||||||
child2.backgroundColor = .green
|
child2.backgroundColor = .green
|
||||||
child2.configureLayout { (layout) in
|
child2.configureLayout { (layout) in
|
||||||
layout.isEnabled = true
|
layout.isEnabled = true
|
||||||
layout.alignSelf = .flexEnd
|
layout.alignSelf = .flexEnd
|
||||||
|
layout.width = 200
|
||||||
|
layout.height = 200
|
||||||
}
|
}
|
||||||
root.addSubview(child2)
|
view.addSubview(child2)
|
||||||
|
|
||||||
let child3 = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
|
let child3 = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
|
||||||
child3.backgroundColor = .yellow
|
child3.backgroundColor = .yellow
|
||||||
child3.configureLayout { (layout) in
|
child3.configureLayout { (layout) in
|
||||||
layout.isEnabled = true
|
layout.isEnabled = true
|
||||||
layout.alignSelf = .flexStart
|
layout.alignSelf = .flexStart
|
||||||
}
|
layout.width = 100
|
||||||
root.addSubview(child3)
|
layout.height = 100
|
||||||
|
|
||||||
root.yoga.applyLayout(preservingOrigin: true)
|
}
|
||||||
|
view.addSubview(child3)
|
||||||
|
|
||||||
|
view.yoga.applyLayout(preservingOrigin: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -14,6 +14,7 @@ final class LayoutInclusionViewController: UIViewController {
|
|||||||
private let contentView: UIView = UIView(frame: .zero)
|
private let contentView: UIView = UIView(frame: .zero)
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
let root = self.view!
|
let root = self.view!
|
||||||
root.backgroundColor = .white
|
root.backgroundColor = .white
|
||||||
root.configureLayout { (layout) in
|
root.configureLayout { (layout) in
|
||||||
@@ -51,9 +52,9 @@ final class LayoutInclusionViewController: UIViewController {
|
|||||||
}
|
}
|
||||||
contentView.addSubview(disappearingView)
|
contentView.addSubview(disappearingView)
|
||||||
|
|
||||||
button.setTitle("Add Blue View", for: UIControlState.selected)
|
button.setTitle("Add Blue View", for: .selected)
|
||||||
button.setTitle("Remove Blue View", for: UIControlState.normal)
|
button.setTitle("Remove Blue View", for: .normal)
|
||||||
button.addTarget(self, action: #selector(buttonWasTapped), for: UIControlEvents.touchUpInside)
|
button.addTarget(self, action: #selector(buttonWasTapped), for: .touchUpInside)
|
||||||
button.configureLayout { (layout) in
|
button.configureLayout { (layout) in
|
||||||
layout.isEnabled = true
|
layout.isEnabled = true
|
||||||
layout.height = 300
|
layout.height = 300
|
||||||
@@ -66,7 +67,7 @@ final class LayoutInclusionViewController: UIViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MARK - UIButton Action
|
// MARK - UIButton Action
|
||||||
func buttonWasTapped() {
|
@objc func buttonWasTapped() {
|
||||||
button.isSelected = !button.isSelected
|
button.isSelected = !button.isSelected
|
||||||
|
|
||||||
button.isUserInteractionEnabled = false
|
button.isUserInteractionEnabled = false
|
||||||
|
Reference in New Issue
Block a user