parent
0e3028956a
commit
1c3af347e4
|
|
@ -16,6 +16,7 @@
|
|||
BD850CB2DF4C9C94FC51226C /* Pods_ReCaptcha_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62BEEA62161F672468CCFD64 /* Pods_ReCaptcha_Example.framework */; };
|
||||
D091B6E053FD250B4757E34C /* Pods_ReCaptcha_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9417A28DC340FF0BC1627B3F /* Pods_ReCaptcha_Tests.framework */; };
|
||||
F206BAD51F8D3FEB00A25807 /* Cartfile in Resources */ = {isa = PBXBuildFile; fileRef = F206BAD41F8D3FEB00A25807 /* Cartfile */; };
|
||||
F211C22220F7E0B100709B26 /* ReCaptcha_Endpoint__Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F211C22120F7E0B100709B26 /* ReCaptcha_Endpoint__Tests.swift */; };
|
||||
F231B3971FEC325A00F82943 /* DispatchQueue__Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F231B3961FEC325A00F82943 /* DispatchQueue__Tests.swift */; };
|
||||
F231B39F1FED4A8C00F82943 /* ReCaptchaDecoder+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F231B39E1FED4A8C00F82943 /* ReCaptchaDecoder+Helper.swift */; };
|
||||
F288E9451F9537760018688D /* ReCaptchaError+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F288E9441F9537760018688D /* ReCaptchaError+Equatable.swift */; };
|
||||
|
|
@ -68,6 +69,7 @@
|
|||
C2A0BDD35B5E219129E9BC65 /* Pods-ReCaptcha_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReCaptcha_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-ReCaptcha_Example/Pods-ReCaptcha_Example.release.xcconfig"; sourceTree = "<group>"; };
|
||||
C8537003ECC47117AF54DCA9 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
|
||||
F206BAD41F8D3FEB00A25807 /* Cartfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Cartfile; path = ../Cartfile; sourceTree = "<group>"; };
|
||||
F211C22120F7E0B100709B26 /* ReCaptcha_Endpoint__Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReCaptcha_Endpoint__Tests.swift; sourceTree = "<group>"; };
|
||||
F21901D91F98D62F00D8E2C9 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = CHANGELOG.md; path = ../CHANGELOG.md; sourceTree = "<group>"; };
|
||||
F231B3961FEC325A00F82943 /* DispatchQueue__Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DispatchQueue__Tests.swift; sourceTree = "<group>"; };
|
||||
F231B39E1FED4A8C00F82943 /* ReCaptchaDecoder+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReCaptchaDecoder+Helper.swift"; sourceTree = "<group>"; };
|
||||
|
|
@ -214,6 +216,7 @@
|
|||
F2E2685D1F7AEE3400CD876D /* ReCaptcha__Tests.swift */,
|
||||
F231B3961FEC325A00F82943 /* DispatchQueue__Tests.swift */,
|
||||
F2AE8611204F3430002E28D7 /* ReCaptchaResult__Tests.swift */,
|
||||
F211C22120F7E0B100709B26 /* ReCaptcha_Endpoint__Tests.swift */,
|
||||
);
|
||||
path = Core;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -606,6 +609,7 @@
|
|||
F2ECCF981EA011370097B199 /* Result+Helpers.swift in Sources */,
|
||||
F231B3971FEC325A00F82943 /* DispatchQueue__Tests.swift in Sources */,
|
||||
F231B39F1FED4A8C00F82943 /* ReCaptchaDecoder+Helper.swift in Sources */,
|
||||
F211C22220F7E0B100709B26 /* ReCaptcha_Endpoint__Tests.swift in Sources */,
|
||||
F2E2685E1F7AEE3400CD876D /* ReCaptcha__Tests.swift in Sources */,
|
||||
F2ECCF931EA009360097B199 /* ReCaptcha+Rx__Tests.swift in Sources */,
|
||||
F288E9451F9537760018688D /* ReCaptchaError+Equatable.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -40,9 +40,8 @@
|
|||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
codeCoverageEnabled = "YES"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
|
|
@ -81,7 +80,6 @@
|
|||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="vXZ-lx-hvc">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14109" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="vXZ-lx-hvc">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="RDW-bD-rSo">
|
||||
<rect key="frame" x="50" y="498" width="275" height="50"/>
|
||||
<rect key="frame" x="50" y="450" width="275" height="50"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="50" id="Bt8-Ou-ht2"/>
|
||||
</constraints>
|
||||
|
|
@ -35,20 +35,20 @@
|
|||
<rect key="frame" x="177" y="323" width="20" height="20"/>
|
||||
</activityIndicatorView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="249" text="" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="o6f-cL-1PF">
|
||||
<rect key="frame" x="20" y="199" width="335" height="269"/>
|
||||
<rect key="frame" x="20" y="247" width="335" height="173"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="resultLabel"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="X8E-G9-9IV">
|
||||
<rect key="frame" x="117" y="568" width="141" height="29"/>
|
||||
<rect key="frame" x="71" y="568" width="233" height="29"/>
|
||||
<segments>
|
||||
<segment title="Default"/>
|
||||
<segment title="Default Endpoint"/>
|
||||
<segment title="Alternate"/>
|
||||
</segments>
|
||||
<connections>
|
||||
<action selector="didPressSegmentedControl:" destination="vXZ-lx-hvc" eventType="valueChanged" id="e5Z-W5-Q1d"/>
|
||||
<action selector="didPressEndpointSegmentedControl:" destination="vXZ-lx-hvc" eventType="valueChanged" id="Sdm-dO-doL"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Uyt-0M-CR7">
|
||||
|
|
@ -76,27 +76,40 @@
|
|||
<constraint firstItem="mGh-Ox-cFf" firstAttribute="leading" secondItem="awK-8H-OCQ" secondAttribute="trailing" constant="10" id="wzo-g0-VEj"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<segmentedControl clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="kq2-ci-l1u">
|
||||
<rect key="frame" x="114" y="520" width="147" height="29"/>
|
||||
<segments>
|
||||
<segment title="Nil Locale"/>
|
||||
<segment title="Chinese"/>
|
||||
</segments>
|
||||
<connections>
|
||||
<action selector="didPressLocaleSegmentedControl:" destination="vXZ-lx-hvc" eventType="valueChanged" id="nSA-f3-8eS"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="o6f-cL-1PF" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leading" constant="20" id="0dV-t0-W0Y"/>
|
||||
<constraint firstAttribute="trailing" secondItem="RDW-bD-rSo" secondAttribute="trailing" constant="50" id="1Lj-Ox-Kl4"/>
|
||||
<constraint firstItem="X8E-G9-9IV" firstAttribute="top" secondItem="kq2-ci-l1u" secondAttribute="bottom" constant="20" id="8ye-XO-bpK"/>
|
||||
<constraint firstItem="X8E-G9-9IV" firstAttribute="centerX" secondItem="kh9-bI-dsS" secondAttribute="centerX" id="92w-cw-lR2"/>
|
||||
<constraint firstItem="2fi-mo-0CV" firstAttribute="top" secondItem="Uyt-0M-CR7" secondAttribute="bottom" constant="20" id="Bfs-p5-IBM"/>
|
||||
<constraint firstItem="o6f-cL-1PF" firstAttribute="centerY" secondItem="kh9-bI-dsS" secondAttribute="centerY" priority="750" id="NMD-ir-PXe"/>
|
||||
<constraint firstItem="X8E-G9-9IV" firstAttribute="top" secondItem="RDW-bD-rSo" secondAttribute="bottom" constant="20" id="Qbw-Jp-xGV"/>
|
||||
<constraint firstItem="RDW-bD-rSo" firstAttribute="top" secondItem="o6f-cL-1PF" secondAttribute="bottom" constant="30" id="TZe-z9-MZS"/>
|
||||
<constraint firstItem="jHc-GP-v1Z" firstAttribute="centerY" secondItem="kh9-bI-dsS" secondAttribute="centerY" id="VOe-WJ-FKo"/>
|
||||
<constraint firstItem="jHc-GP-v1Z" firstAttribute="centerX" secondItem="kh9-bI-dsS" secondAttribute="centerX" id="XkT-zr-eUO"/>
|
||||
<constraint firstItem="kq2-ci-l1u" firstAttribute="top" secondItem="RDW-bD-rSo" secondAttribute="bottom" constant="20" id="Y4H-6k-gDx"/>
|
||||
<constraint firstAttribute="trailing" secondItem="o6f-cL-1PF" secondAttribute="trailing" constant="20" id="c74-nm-rgi"/>
|
||||
<constraint firstAttribute="trailing" secondItem="RDW-bD-rSo" secondAttribute="trailing" constant="50" id="c7q-Rw-n0F"/>
|
||||
<constraint firstItem="RDW-bD-rSo" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leading" constant="50" id="iXO-hP-XZ7"/>
|
||||
<constraint firstItem="kq2-ci-l1u" firstAttribute="centerX" secondItem="kh9-bI-dsS" secondAttribute="centerX" id="ice-pp-mVe"/>
|
||||
<constraint firstItem="Uyt-0M-CR7" firstAttribute="top" secondItem="X8E-G9-9IV" secondAttribute="bottom" constant="20" id="nIE-QT-RpQ"/>
|
||||
<constraint firstItem="Uyt-0M-CR7" firstAttribute="centerX" secondItem="kh9-bI-dsS" secondAttribute="centerX" id="qfG-68-ySO"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="endpointSegmentedControl" destination="X8E-G9-9IV" id="jAb-fU-Yu5"/>
|
||||
<outlet property="label" destination="o6f-cL-1PF" id="KQV-3X-RKr"/>
|
||||
<outlet property="segmentedControl" destination="X8E-G9-9IV" id="sS0-3k-Alu"/>
|
||||
<outlet property="localeSegmentedControl" destination="kq2-ci-l1u" id="gL7-du-K6l"/>
|
||||
<outlet property="spinner" destination="jHc-GP-v1Z" id="gRn-JW-FIK"/>
|
||||
<outlet property="visibleChallengeSwitch" destination="mGh-Ox-cFf" id="R13-nD-EXL"/>
|
||||
</connections>
|
||||
|
|
|
|||
|
|
@ -21,23 +21,40 @@ class ViewController: UIViewController {
|
|||
private var recaptcha: ReCaptcha!
|
||||
private var disposeBag = DisposeBag()
|
||||
|
||||
private var locale: Locale?
|
||||
private var endpoint = ReCaptcha.Endpoint.default
|
||||
|
||||
@IBOutlet private weak var label: UILabel!
|
||||
@IBOutlet private weak var spinner: UIActivityIndicatorView!
|
||||
@IBOutlet private weak var segmentedControl: UISegmentedControl!
|
||||
@IBOutlet private weak var localeSegmentedControl: UISegmentedControl!
|
||||
@IBOutlet private weak var endpointSegmentedControl: UISegmentedControl!
|
||||
@IBOutlet private weak var visibleChallengeSwitch: UISwitch!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
setupReCaptcha(endpoint: .default)
|
||||
setupReCaptcha()
|
||||
}
|
||||
|
||||
@IBAction func didPressSegmentedControl(_ sender: UISegmentedControl) {
|
||||
@IBAction func didPressEndpointSegmentedControl(_ sender: UISegmentedControl) {
|
||||
label.text = ""
|
||||
switch sender.selectedSegmentIndex {
|
||||
case 0: setupReCaptcha(endpoint: .default)
|
||||
case 1: setupReCaptcha(endpoint: .alternate)
|
||||
case 0: endpoint = .default
|
||||
case 1: endpoint = .alternate
|
||||
default: assertionFailure("invalid index")
|
||||
}
|
||||
|
||||
setupReCaptcha()
|
||||
}
|
||||
|
||||
@IBAction func didPressLocaleSegmentedControl(_ sender: UISegmentedControl) {
|
||||
label.text = ""
|
||||
switch sender.selectedSegmentIndex {
|
||||
case 0: locale = nil
|
||||
case 1: locale = Locale(identifier: "zh-CN")
|
||||
default: assertionFailure("invalid index")
|
||||
}
|
||||
|
||||
setupReCaptcha()
|
||||
}
|
||||
|
||||
@IBAction private func didPressButton(button: UIButton) {
|
||||
|
|
@ -66,7 +83,7 @@ class ViewController: UIViewController {
|
|||
.disposed(by: disposeBag)
|
||||
|
||||
isEnabled
|
||||
.bind(to: segmentedControl.rx.isEnabled)
|
||||
.bind(to: endpointSegmentedControl.rx.isEnabled)
|
||||
.disposed(by: disposeBag)
|
||||
|
||||
validate
|
||||
|
|
@ -89,9 +106,9 @@ class ViewController: UIViewController {
|
|||
.disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
private func setupReCaptcha(endpoint: ReCaptcha.Endpoint) {
|
||||
private func setupReCaptcha() {
|
||||
// swiftlint:disable:next force_try
|
||||
recaptcha = try! ReCaptcha(endpoint: endpoint)
|
||||
recaptcha = try! ReCaptcha(endpoint: endpoint, locale: locale)
|
||||
|
||||
recaptcha.configureWebView { [weak self] webview in
|
||||
webview.frame = self?.view.bounds ?? CGRect.zero
|
||||
|
|
|
|||
|
|
@ -259,7 +259,7 @@ class ReCaptchaWebViewManager__Tests: XCTestCase {
|
|||
|
||||
func test__Endpoint_Setup() {
|
||||
let exp = expectation(description: "setup endpoint")
|
||||
let endpoint = ReCaptcha.Endpoint.alternate.url
|
||||
let endpoint = ReCaptcha.Endpoint.alternate.getURL(locale: nil)
|
||||
var result: ReCaptchaResult?
|
||||
|
||||
let manager = ReCaptchaWebViewManager(messageBody: "{token: endpoint}", endpoint: endpoint)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// ReCaptcha_Endpoint__.swift
|
||||
// ReCaptcha
|
||||
//
|
||||
// Created by Flávio Caetano on 12/07/18.
|
||||
// Copyright © 2018 ReCaptcha. All rights reserved.
|
||||
//
|
||||
|
||||
@testable import ReCaptcha
|
||||
import XCTest
|
||||
|
||||
class ReCaptcha_Endpoint__Tests: XCTestCase {
|
||||
|
||||
private let endpoint = ReCaptcha.Endpoint.default
|
||||
private let endpointURL = "https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit"
|
||||
|
||||
// MARK: - Locale
|
||||
|
||||
func test__Locale__Nil() {
|
||||
XCTAssertEqual(endpoint.getURL(locale: nil), endpointURL)
|
||||
}
|
||||
|
||||
func test__Locale__Valid() {
|
||||
let locale = Locale(identifier: "pt-BR")
|
||||
XCTAssertEqual(endpoint.getURL(locale: locale), "\(endpointURL)&hl=pt-BR")
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ class ReCaptcha_UITests: XCTestCase {
|
|||
|
||||
func test__Validate__Default_Endpoint() {
|
||||
let app = XCUIApplication()
|
||||
app.segmentedControls.buttons["Default"].tap()
|
||||
app.segmentedControls.buttons["Default Endpoint"].tap()
|
||||
app.switches["Switch"].tap()
|
||||
app.buttons["Validate"].tap()
|
||||
|
||||
|
|
|
|||
|
|
@ -30,10 +30,14 @@ public class ReCaptcha {
|
|||
/// Alternate endpoint. Points to https://www.recaptcha.net/recaptcha/api.js
|
||||
case alternate
|
||||
|
||||
internal var url: String {
|
||||
internal func getURL(locale: Locale?) -> String {
|
||||
let localeAppendix = locale.map { "&hl=\($0.identifier)" } ?? ""
|
||||
switch self {
|
||||
case .default: return "https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit"
|
||||
case .alternate: return "https://www.recaptcha.net/recaptcha/api.js?onload=onloadCallback&render=explicit"
|
||||
case .default:
|
||||
return "https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" + localeAppendix
|
||||
case .alternate:
|
||||
return "https://www.recaptcha.net/recaptcha/api.js?onload=onloadCallback&render=explicit"
|
||||
+ localeAppendix
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -106,6 +110,7 @@ public class ReCaptcha {
|
|||
- infoPlistKey: The API key retrived from the application's Info.plist
|
||||
- baseURL: The base URL sent to the ReCaptcha init
|
||||
- infoPlistURL: The base URL retrieved from the application's Info.plist
|
||||
- locale: A locale value to translate ReCaptcha into a different language
|
||||
|
||||
Initializes a ReCaptcha object
|
||||
|
||||
|
|
@ -121,7 +126,12 @@ public class ReCaptcha {
|
|||
Info.plist.
|
||||
- Throws: Rethrows any exceptions thrown by `String(contentsOfFile:)`
|
||||
*/
|
||||
public convenience init(apiKey: String? = nil, baseURL: URL? = nil, endpoint: Endpoint = .default) throws {
|
||||
public convenience init(
|
||||
apiKey: String? = nil,
|
||||
baseURL: URL? = nil,
|
||||
endpoint: Endpoint = .default,
|
||||
locale: Locale? = nil
|
||||
) throws {
|
||||
let infoDict = Bundle.main.infoDictionary
|
||||
|
||||
let plistApiKey = infoDict?[Constants.InfoDictKeys.APIKey] as? String
|
||||
|
|
@ -133,7 +143,7 @@ public class ReCaptcha {
|
|||
html: config.html,
|
||||
apiKey: config.apiKey,
|
||||
baseURL: config.baseURL,
|
||||
endpoint: endpoint.url
|
||||
endpoint: endpoint.getURL(locale: locale)
|
||||
))
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue