feature/minimize_pan_modal_changes #2
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
Pod::Spec.new do |s|
|
Pod::Spec.new do |s|
|
||||||
s.name = 'PanModal'
|
s.name = 'PanModal'
|
||||||
s.version = '1.2.7'
|
s.version = '1.3.0'
|
||||||
s.summary = 'PanModal is an elegant and highly customizable presentation API for constructing bottom sheet modals on iOS.'
|
s.summary = 'PanModal is an elegant and highly customizable presentation API for constructing bottom sheet modals on iOS.'
|
||||||
|
|
||||||
# This description is used to generate tags and improve search results.
|
# This description is used to generate tags and improve search results.
|
||||||
|
|
@ -18,10 +18,10 @@ Pod::Spec.new do |s|
|
||||||
# * Finally, don't worry about the indent, CocoaPods strips it!
|
# * Finally, don't worry about the indent, CocoaPods strips it!
|
||||||
|
|
||||||
s.description = 'PanModal is an elegant and highly customizable presentation API for constructing bottom sheet modals on iOS.'
|
s.description = 'PanModal is an elegant and highly customizable presentation API for constructing bottom sheet modals on iOS.'
|
||||||
s.homepage = 'https://github.com/slackhq/PanModal'
|
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/TIPanModal'
|
||||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||||
s.author = { 'slack' => 'opensource@slack.com' }
|
s.author = { 'slack' => 'opensource@slack.com' }
|
||||||
s.source = { :git => 'https://github.com/slackhq/PanModal.git', :tag => s.version.to_s }
|
s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/TIPanModal.git', :tag => s.version.to_s }
|
||||||
s.social_media_url = 'https://twitter.com/slackhq'
|
s.social_media_url = 'https://twitter.com/slackhq'
|
||||||
s.ios.deployment_target = '10.0'
|
s.ios.deployment_target = '10.0'
|
||||||
s.swift_version = '5.0'
|
s.swift_version = '5.0'
|
||||||
|
|
|
||||||
|
|
@ -112,19 +112,15 @@ open class PanModalPresentationController: UIPresentationController {
|
||||||
Background view used as an overlay over the presenting view
|
Background view used as an overlay over the presenting view
|
||||||
*/
|
*/
|
||||||
private lazy var backgroundView: DimmedView = {
|
private lazy var backgroundView: DimmedView = {
|
||||||
let view: DimmedView
|
let view: DimmedView = presentable?.dimmedView ?? DimmedView()
|
||||||
let type = presentable?.dimmedViewType ?? .opaque
|
|
||||||
|
|
||||||
if let color = presentable?.panModalBackgroundColor {
|
if let color = presentable?.panModalBackgroundColor {
|
||||||
view = DimmedView(presentingController: presentingViewController, dimColor: color, appearanceType: type)
|
view.backgroundColor = color
|
||||||
} else {
|
|
||||||
view = DimmedView(presentingController: presentingViewController, appearanceType: type)
|
|
||||||
}
|
}
|
||||||
|
view.didTap = { [weak self] _ in
|
||||||
view.didTap = { [weak self] in
|
if self?.presentable?.allowsTapToDismiss == true {
|
||||||
self?.presentable?.onTapToDismiss?()
|
self?.presentable?.onTapToDismiss?()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -138,6 +134,16 @@ open class PanModalPresentationController: UIPresentationController {
|
||||||
return PanContainerView(presentedView: presentedViewController.view, frame: frame)
|
return PanContainerView(presentedView: presentedViewController.view, frame: frame)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
/**
|
||||||
|
Drag Indicator View
|
||||||
|
*/
|
||||||
|
private lazy var dragIndicatorView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = presentable?.dragIndicatorBackgroundColor
|
||||||
|
view.layer.cornerRadius = Constants.dragIndicatorSize.height / 2.0
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Override presented view to return the pan container wrapper
|
Override presented view to return the pan container wrapper
|
||||||
*/
|
*/
|
||||||
|
|
@ -179,7 +185,6 @@ open class PanModalPresentationController: UIPresentationController {
|
||||||
layoutBackgroundView(in: containerView)
|
layoutBackgroundView(in: containerView)
|
||||||
layoutPresentedView(in: containerView)
|
layoutPresentedView(in: containerView)
|
||||||
configureScrollViewInsets()
|
configureScrollViewInsets()
|
||||||
configureShadowIfNeeded()
|
|
||||||
|
|
||||||
guard let coordinator = presentedViewController.transitionCoordinator else {
|
guard let coordinator = presentedViewController.transitionCoordinator else {
|
||||||
backgroundView.dimState = .max
|
backgroundView.dimState = .max
|
||||||
|
|
@ -213,6 +218,7 @@ open class PanModalPresentationController: UIPresentationController {
|
||||||
so hiding it on view dismiss means avoiding visual bugs
|
so hiding it on view dismiss means avoiding visual bugs
|
||||||
*/
|
*/
|
||||||
coordinator.animate(alongsideTransition: { [weak self] _ in
|
coordinator.animate(alongsideTransition: { [weak self] _ in
|
||||||
|
self?.dragIndicatorView.alpha = 0.0
|
||||||
self?.backgroundView.dimState = .off
|
self?.backgroundView.dimState = .off
|
||||||
self?.presentingViewController.setNeedsStatusBarAppearanceUpdate()
|
self?.presentingViewController.setNeedsStatusBarAppearanceUpdate()
|
||||||
})
|
})
|
||||||
|
|
@ -352,6 +358,10 @@ private extension PanModalPresentationController {
|
||||||
containerView.addSubview(presentedView)
|
containerView.addSubview(presentedView)
|
||||||
containerView.addGestureRecognizer(panGestureRecognizer)
|
containerView.addGestureRecognizer(panGestureRecognizer)
|
||||||
|
|
||||||
|
if presentable.showDragIndicator {
|
||||||
|
addDragIndicatorView(to: presentedView)
|
||||||
|
}
|
||||||
|
|
||||||
if presentable.shouldRoundTopCorners {
|
if presentable.shouldRoundTopCorners {
|
||||||
addRoundedCorners(to: presentedView)
|
addRoundedCorners(to: presentedView)
|
||||||
}
|
}
|
||||||
|
|
@ -371,8 +381,10 @@ private extension PanModalPresentationController {
|
||||||
let adjustedSize = CGSize(width: frame.size.width, height: frame.size.height - anchoredYPosition)
|
let adjustedSize = CGSize(width: frame.size.width, height: frame.size.height - anchoredYPosition)
|
||||||
let panFrame = panContainerView.frame
|
let panFrame = panContainerView.frame
|
||||||
panContainerView.frame.size = frame.size
|
panContainerView.frame.size = frame.size
|
||||||
|
|
||||||
|
let positions = [shortFormYPosition, mediumFormYPosition, longFormYPosition]
|
||||||
|
|
||||||
if ![shortFormYPosition, mediumFormYPosition, longFormYPosition].contains(panFrame.origin.y) {
|
if !positions.contains(panFrame.origin.y) {
|
||||||
// if the container is already in the correct position, no need to adjust positioning
|
// if the container is already in the correct position, no need to adjust positioning
|
||||||
// (rotations & size changes cause positioning to be out of sync)
|
// (rotations & size changes cause positioning to be out of sync)
|
||||||
let yPosition = panFrame.origin.y - panFrame.height + frame.height
|
let yPosition = panFrame.origin.y - panFrame.height + frame.height
|
||||||
|
|
@ -405,6 +417,19 @@ private extension PanModalPresentationController {
|
||||||
backgroundView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
|
backgroundView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Adds the drag indicator view to the view hierarchy
|
||||||
|
& configures its layout constraints.
|
||||||
|
*/
|
||||||
|
func addDragIndicatorView(to view: UIView) {
|
||||||
|
view.addSubview(dragIndicatorView)
|
||||||
|
dragIndicatorView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
dragIndicatorView.bottomAnchor.constraint(equalTo: view.topAnchor, constant: -Constants.indicatorYOffset).isActive = true
|
||||||
|
dragIndicatorView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
|
||||||
|
dragIndicatorView.widthAnchor.constraint(equalToConstant: Constants.dragIndicatorSize.width).isActive = true
|
||||||
|
dragIndicatorView.heightAnchor.constraint(equalToConstant: Constants.dragIndicatorSize.height).isActive = true
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Calculates & stores the layout anchor points & options
|
Calculates & stores the layout anchor points & options
|
||||||
*/
|
*/
|
||||||
|
|
@ -443,7 +468,15 @@ private extension PanModalPresentationController {
|
||||||
Set the appropriate contentInset as the configuration within this class
|
Set the appropriate contentInset as the configuration within this class
|
||||||
offsets it
|
offsets it
|
||||||
*/
|
*/
|
||||||
scrollView.contentInset.bottom = presentingViewController.bottomLayoutGuide.length
|
let bottomInset: CGFloat
|
||||||
|
|
||||||
|
if #available(iOS 11.0, *) {
|
||||||
|
bottomInset = presentingViewController.view.safeAreaInsets.bottom
|
||||||
|
} else {
|
||||||
|
bottomInset = presentingViewController.bottomLayoutGuide.length
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollView.contentInset.bottom = bottomInset
|
||||||
|
|
||||||
/**
|
/**
|
||||||
As we adjust the bounds during `handleScrollViewTopBounce`
|
As we adjust the bounds during `handleScrollViewTopBounce`
|
||||||
|
|
@ -454,13 +487,6 @@ private extension PanModalPresentationController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func configureShadowIfNeeded() {
|
|
||||||
let type = presentable?.dimmedViewType ?? .opaque
|
|
||||||
|
|
||||||
if case let .transparentWithShadow(shadow) = type {
|
|
||||||
containerView?.configure(shadow: shadow)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Pan Gesture Event Handler
|
// MARK: - Pan Gesture Event Handler
|
||||||
|
|
@ -514,11 +540,13 @@ private extension PanModalPresentationController {
|
||||||
if velocity.y < 0 {
|
if velocity.y < 0 {
|
||||||
transition(to: .longForm)
|
transition(to: .longForm)
|
||||||
|
|
||||||
} else if nearest(to: presentedView.frame.minY, inValues: [mediumFormYPosition, containerView.bounds.height]) == mediumFormYPosition
|
} else if nearest(to: presentedView.frame.minY,
|
||||||
|
inValues: [mediumFormYPosition, containerView.bounds.height]) == mediumFormYPosition
|
||||||
&& presentedView.frame.minY < mediumFormYPosition {
|
&& presentedView.frame.minY < mediumFormYPosition {
|
||||||
transition(to: .mediumForm)
|
transition(to: .mediumForm)
|
||||||
|
|
||||||
} else if (nearest(to: presentedView.frame.minY, inValues: [longFormYPosition, containerView.bounds.height]) == longFormYPosition
|
} else if (nearest(to: presentedView.frame.minY,
|
||||||
|
inValues: [longFormYPosition, containerView.bounds.height]) == longFormYPosition
|
||||||
&& presentedView.frame.minY < shortFormYPosition) || allowsDragToDismiss == false {
|
&& presentedView.frame.minY < shortFormYPosition) || allowsDragToDismiss == false {
|
||||||
transition(to: .shortForm)
|
transition(to: .shortForm)
|
||||||
|
|
||||||
|
|
@ -532,7 +560,11 @@ private extension PanModalPresentationController {
|
||||||
The `containerView.bounds.height` is used to determine
|
The `containerView.bounds.height` is used to determine
|
||||||
how close the presented view is to the bottom of the screen
|
how close the presented view is to the bottom of the screen
|
||||||
*/
|
*/
|
||||||
let position = nearest(to: presentedView.frame.minY, inValues: [containerView.bounds.height, shortFormYPosition, mediumFormYPosition, longFormYPosition])
|
let position = nearest(to: presentedView.frame.minY,
|
||||||
|
inValues: [containerView.bounds.height,
|
||||||
|
shortFormYPosition,
|
||||||
|
mediumFormYPosition,
|
||||||
|
longFormYPosition])
|
||||||
|
|
||||||
if position == longFormYPosition {
|
if position == longFormYPosition {
|
||||||
transition(to: .longForm)
|
transition(to: .longForm)
|
||||||
|
|
@ -659,7 +691,7 @@ private extension PanModalPresentationController {
|
||||||
|
|
||||||
let maxHeight = UIScreen.main.bounds.height - longFormYPosition
|
let maxHeight = UIScreen.main.bounds.height - longFormYPosition
|
||||||
|
|
||||||
backgroundView.dimState = .percent(1 - presentedView.frame.origin.y / maxHeight)
|
backgroundView.dimState = .percent(1.0 - presentedView.frame.origin.y / maxHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -841,6 +873,12 @@ private extension PanModalPresentationController {
|
||||||
byRoundingCorners: [.topLeft, .topRight],
|
byRoundingCorners: [.topLeft, .topRight],
|
||||||
cornerRadii: CGSize(width: radius, height: radius))
|
cornerRadii: CGSize(width: radius, height: radius))
|
||||||
|
|
||||||
|
// Draw around the drag indicator view, if displayed
|
||||||
|
if presentable?.showDragIndicator == true {
|
||||||
|
let indicatorLeftEdgeXPos = view.bounds.width/2.0 - Constants.dragIndicatorSize.width/2.0
|
||||||
|
drawAroundDragIndicator(currentPath: path, indicatorLeftEdgeXPos: indicatorLeftEdgeXPos)
|
||||||
|
}
|
||||||
|
|
||||||
// Set path as a mask to display optional drag indicator view & rounded corners
|
// Set path as a mask to display optional drag indicator view & rounded corners
|
||||||
let mask = CAShapeLayer()
|
let mask = CAShapeLayer()
|
||||||
mask.path = path.cgPath
|
mask.path = path.cgPath
|
||||||
|
|
|
||||||
|
|
@ -47,13 +47,9 @@ public extension PanModalPresentable where Self: UIViewController {
|
||||||
return .contentHeight(scrollView.contentSize.height)
|
return .contentHeight(scrollView.contentSize.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
var dimmedViewType: DimmedView.AppearanceType {
|
var dimmedView: DimmedView? {
|
||||||
.opaque
|
DimmedView()
|
||||||
}
|
}
|
||||||
|
|
||||||
var presentationDetents: [ModalViewPresentationDetent] {
|
|
||||||
[]
|
|
||||||
}
|
|
||||||
|
|
||||||
var cornerRadius: CGFloat {
|
var cornerRadius: CGFloat {
|
||||||
return 8.0
|
return 8.0
|
||||||
|
|
|
||||||
|
|
@ -57,9 +57,7 @@ public protocol PanModalPresentable: AnyObject {
|
||||||
*/
|
*/
|
||||||
var longFormHeight: PanModalHeight { get }
|
var longFormHeight: PanModalHeight { get }
|
||||||
|
|
||||||
var presentationDetents: [ModalViewPresentationDetent] { get }
|
var dimmedView: DimmedView? { get }
|
||||||
|
|
||||||
var dimmedViewType: DimmedView.AppearanceType { get }
|
|
||||||
/**
|
/**
|
||||||
The corner radius used when `shouldRoundTopCorners` is enabled.
|
The corner radius used when `shouldRoundTopCorners` is enabled.
|
||||||
|
|
||||||
|
|
@ -100,6 +98,13 @@ public protocol PanModalPresentable: AnyObject {
|
||||||
*/
|
*/
|
||||||
var panModalBackgroundColor: UIColor { get }
|
var panModalBackgroundColor: UIColor { get }
|
||||||
|
|
||||||
|
/**
|
||||||
|
The drag indicator view color.
|
||||||
|
|
||||||
|
Default value is light gray.
|
||||||
|
*/
|
||||||
|
var dragIndicatorBackgroundColor: UIColor { get }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
We configure the panScrollable's scrollIndicatorInsets interally so override this value
|
We configure the panScrollable's scrollIndicatorInsets interally so override this value
|
||||||
to set custom insets.
|
to set custom insets.
|
||||||
|
|
@ -132,7 +137,9 @@ public protocol PanModalPresentable: AnyObject {
|
||||||
Default value is true.
|
Default value is true.
|
||||||
*/
|
*/
|
||||||
var allowsDragToDismiss: Bool { get }
|
var allowsDragToDismiss: Bool { get }
|
||||||
|
|
||||||
var onTapToDismiss: (() -> Void)? { get }
|
var onTapToDismiss: (() -> Void)? { get }
|
||||||
|
|
||||||
var onDragToDismiss: (() -> Void)? { get }
|
var onDragToDismiss: (() -> Void)? { get }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -165,6 +172,13 @@ public protocol PanModalPresentable: AnyObject {
|
||||||
*/
|
*/
|
||||||
var shouldRoundTopCorners: Bool { get }
|
var shouldRoundTopCorners: Bool { get }
|
||||||
|
|
||||||
|
/**
|
||||||
|
A flag to determine if a drag indicator should be shown
|
||||||
|
above the pan modal container view.
|
||||||
|
|
||||||
|
Default value is true.
|
||||||
|
*/
|
||||||
|
var showDragIndicator: Bool { get }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Asks the delegate if the pan modal should respond to the pan modal gesture recognizer.
|
Asks the delegate if the pan modal should respond to the pan modal gesture recognizer.
|
||||||
|
|
|
||||||
|
|
@ -11,30 +11,13 @@ import UIKit
|
||||||
/**
|
/**
|
||||||
A dim view for use as an overlay over content you want dimmed.
|
A dim view for use as an overlay over content you want dimmed.
|
||||||
*/
|
*/
|
||||||
public class DimmedView: UIView {
|
open class DimmedView: UIView {
|
||||||
|
|
||||||
public enum AppearanceType {
|
|
||||||
|
|
||||||
case transparent
|
|
||||||
case transparentWithShadow(ShadowConfigurator)
|
|
||||||
case opaque
|
|
||||||
|
|
||||||
var isTransparent: Bool {
|
|
||||||
switch self {
|
|
||||||
case .transparent, .transparentWithShadow:
|
|
||||||
return true
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Represents the possible states of the dimmed view.
|
Represents the possible states of the dimmed view.
|
||||||
max, off or a percentage of dimAlpha.
|
max, off or a percentage of dimAlpha.
|
||||||
*/
|
*/
|
||||||
enum DimState {
|
public enum DimState {
|
||||||
case max
|
case max
|
||||||
case off
|
case off
|
||||||
case percent(CGFloat)
|
case percent(CGFloat)
|
||||||
|
|
@ -47,77 +30,52 @@ public class DimmedView: UIView {
|
||||||
*/
|
*/
|
||||||
var dimState: DimState = .off {
|
var dimState: DimState = .off {
|
||||||
didSet {
|
didSet {
|
||||||
guard !appearanceType.isTransparent else {
|
onChange(dimState: dimState)
|
||||||
alpha = .zero
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch dimState {
|
|
||||||
case .max:
|
|
||||||
alpha = 1.0
|
|
||||||
case .off:
|
|
||||||
alpha = 0.0
|
|
||||||
case .percent(let percentage):
|
|
||||||
alpha = max(0.0, min(1.0, percentage))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
weak var presentingController: UIViewController?
|
|
||||||
var appearanceType: AppearanceType
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The closure to be executed when a tap occurs
|
The closure to be executed when a tap occurs
|
||||||
*/
|
*/
|
||||||
var didTap: (() -> Void)?
|
var didTap: ((_ recognizer: UITapGestureRecognizer) -> Void)?
|
||||||
|
|
||||||
|
/**
|
||||||
|
Tap gesture recognizer
|
||||||
|
*/
|
||||||
|
private lazy var tapGesture: UIGestureRecognizer = {
|
||||||
|
return UITapGestureRecognizer(target: self, action: #selector(didTapView))
|
||||||
|
}()
|
||||||
|
|
||||||
// MARK: - Initializers
|
// MARK: - Initializers
|
||||||
|
|
||||||
init(presentingController: UIViewController? = nil,
|
init() {
|
||||||
dimColor: UIColor = UIColor.black.withAlphaComponent(0.7),
|
|
||||||
appearanceType: AppearanceType) {
|
|
||||||
|
|
||||||
self.presentingController = presentingController
|
|
||||||
self.appearanceType = appearanceType
|
|
||||||
|
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
|
|
||||||
alpha = 0.0
|
alpha = 0.0
|
||||||
backgroundColor = dimColor
|
|
||||||
|
|
||||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapView))
|
|
||||||
addGestureRecognizer(tapGesture)
|
addGestureRecognizer(tapGesture)
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(*, unavailable)
|
|
||||||
required public init?(coder aDecoder: NSCoder) {
|
required public init?(coder aDecoder: NSCoder) {
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Overrides
|
|
||||||
|
|
||||||
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
|
||||||
if appearanceType.isTransparent {
|
|
||||||
let subviews = presentingController?.view.subviews.reversed() ?? []
|
|
||||||
|
|
||||||
for subview in subviews {
|
|
||||||
if let hittedView = subview.hitTest(point, with: event) {
|
|
||||||
return hittedView
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.hitTest(point, with: event)
|
|
||||||
}
|
|
||||||
|
|
||||||
public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
|
||||||
!appearanceType.isTransparent
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Event Handlers
|
// MARK: - Event Handlers
|
||||||
|
|
||||||
@objc private func didTapView() {
|
@objc private func didTapView(sender: UITapGestureRecognizer) {
|
||||||
didTap?()
|
didTap?(sender)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Subclass override
|
||||||
|
|
||||||
|
open func onChange(dimState: DimState) {
|
||||||
|
switch dimState {
|
||||||
|
case .max:
|
||||||
|
alpha = 1.0
|
||||||
|
case .off:
|
||||||
|
alpha = 0.0
|
||||||
|
case .percent(let percentage):
|
||||||
|
alpha = max(0.0, min(1.0, percentage))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue